题目 3330: 蓝桥杯2025年第十六届省赛真题-01 串

攻克蓝桥杯2025省赛真题:01串统计问题(附完整代码+思路拆解)

蓝桥杯省赛的算法题往往兼具"思维性"和"实战性",2025年第十六届省赛的「01串」问题就是典型------看似简单的统计问题,暗藏对大数处理和数学规律的考察。本文会从题意拆解、核心思路、代码解析到优化细节,带你彻底吃透这道题。

一、题目解读

题目描述

我们有一个无限长的01串,它由 0, 1, 2, 3, ... 这些自然数的二进制表示依次拼接 而成,前若干位形如:011011100101110111 · · ·

给定一个正整数 x,请计算这个串的前 x 位中包含多少个数字 1

输入输出与样例

  • 输入:一个正整数 x1 ≤ x ≤ 10^18
  • 输出:前 x 位中 1 的个数
  • 样例:
    输入 7 → 前7位是 0 1 1 0 1 1 1 → 输出 5

核心痛点

如果直接暴力拼接二进制数、逐位统计,当 x 达到 10^18 时,时间和空间都会直接爆炸。必须找到二进制数的分布规律,用"分段统计"替代暴力遍历

二、解题思路拆解

第一步:简化问题------跳过无意义的bin(0)

自然数0的二进制是 "0",仅占1位且无数字1,因此我们可以先把 x 减1(跳过这一位),从自然数1的二进制开始统计,最终结果不受影响。

第二步:按二进制位数「分段」

观察自然数的二进制长度规律:

二进制位数 t 数字范围 数字个数 总占用位数
1位 [1](2⁰ ~ 2¹-1) 2⁰ = 1 1×1 = 1
2位 [2,3](2¹ ~ 2²-1) 2¹ = 2 2×2 = 4
3位 [4,7](2² ~ 2³-1) 2² = 4 3×4 = 12
... ... 2^(t-1) t×2^(t-1)

总结规律:

  • t 位二进制数的范围是 [2^(t-1), 2^t - 1]
  • 共有 2^(t-1) 个这样的数;
  • 总共占用 t × 2^(t-1) 位。

第三步:整段统计1的数量(核心公式)

对于每一段 t 位的二进制数,我们可以用数学公式直接算出其中1的总数,无需逐个统计:

  1. 最高位的1 :所有 t 位二进制数的最高位都是1,因此这部分有 2^(t-1) 个1;
  2. 剩余t-1位的1 :剩下的 t-1 位中,每一位的0和1是均匀分布的(比如2位二进制数的低位:2是10、3是11,低位0和1各出现1次)。因此每一位有 2^(t-2) 个1,t-1 位总共有 (t-1) × 2^(t-2) 个1;
  3. 整段总数2^(t-1) + (t-1) × 2^(t-2)

第四步:分阶段处理

  1. 整段处理 :如果当前剩余需要统计的位数 x ≥ 该段总位数(t×2^(t-1)),则直接把该段的1的数量加到答案中,同时 x 减去该段总位数,t 加1继续处理下一段;
  2. 剩余部分处理 :当剩余 x 不足一个完整段时,从该段的第一个数(2^(t-1))开始,逐个拼接二进制位、统计1的个数,直到 x 被消耗完。

三、完整代码与逐行解析

先贴出可直接运行的代码(优化了pow精度问题,原代码的pow可能因double精度丢失出错):

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

// 数据类型必须用long long,否则1e18会溢出
typedef long long LL;

LL x, res = 0, t = 1;

// 将数字i转二进制(高位到低位),统计前x位中的1(x会逐步消耗)
void check(LL i) {
    vector<int> bin;
    // 先把i转成二进制(低位在前)
    while (i) {
        bin.push_back(i % 2);
        i /= 2;
    }
    // 从高位到低位遍历(逆序),消耗x并统计1
    for (int j = bin.size() - 1; j >= 0 && x > 0; j--, x--) {
        if (bin[j] == 1) res++;
    }
}

// 快速幂计算2^k(替代pow,避免double精度问题)
LL pow2(LL k) {
    LL ans = 1;
    while (k--) ans <<= 1;
    return ans;
}

int main() {
    cin >> x;
    // 跳过bin(0)(仅1位0,无1)
    if (x >= 1) x--;
    
    // 整段处理:按二进制位数t分段统计
    while (x >= t * pow2(t - 1)) {
        // 计算当前t位段中1的总数
        LL cnt1 = pow2(t - 1);          // 最高位的1的数量
        LL cnt2 = (t - 1) * pow2(t - 2); // 剩余t-1位的1的数量
        res += cnt1 + cnt2;
        // 消耗当前段的总位数
        x -= t * pow2(t - 1);
        // 处理下一个长度的二进制数
        t++;
    }
    
    // 处理剩余不足整段的部分:逐个数字拼接统计
    for (LL i = pow2(t - 1); x > 0; i++) {
        check(i);
    }
    
    cout << res << endl;
    return 0;
}

代码关键解析

  1. pow2函数:替代系统pow(返回double),用位运算实现2的幂次,避免大数精度丢失(比如t=60时,2^59用double会丢失精度);
  2. check函数:核心是"逐位消耗x"------把数字转二进制后,从高位到低位遍历,每遍历一位就把x减1,若该位是1则答案加1,直到x为0;
  3. 整段处理的while循环:核心逻辑,用数学公式批量统计整段的1,时间复杂度极低(t最多到60,因为2^60已超过1e18);
  4. 剩余部分的for循环:不足整段时,暴力拼接二进制位,由于剩余x最多是t×2^(t-1)(t≤60),这部分循环次数极少,不会超时。

四、样例验证(输入7)

我们用样例输入7走一遍流程,验证结果:

  1. 初始x=7 → 跳过bin(0),x=6;
  2. 整段处理:
    • t=1:t×2^(t-1)=1×1=1 ≤ 6 → 统计1位段的1(仅数字1,二进制1,1个1)→ res=1,x=6-1=5,t=2;
    • t=2:t×2^(t-1)=2×2=4 ≤5 → 统计2位段的1(数字210、311,总共有1+2=3个1)→ res=1+3=4,x=5-4=1,t=3;
    • t=3:t×2^(t-1)=3×4=12 >1 → 退出整段循环;
  3. 剩余部分处理:
    • t=3,起始数是4(2^(3-1)=4),调用check(4):4的二进制是100
    • 遍历高位到低位:第一位是1 → res=4+1=5,x=1-1=0 → 结束;
  4. 最终输出res=5,与样例一致。

五、注意事项与优化

  1. 数据类型 :全程必须用long longint最多存到2e9,无法处理1e18;
  2. pow精度问题 :原代码用pow(2, t-1)存在隐患,比如t=60时,2^59的double表示会丢失低位,必须用位运算/快速幂替代;
  3. 边界条件
    • x=1:仅bin(0),输出0;
    • x=2:bin(0)+bin(1) → 01,输出1;
    • x=0:题目规定x是正整数,无需处理;
  4. 时间复杂度:整段处理的循环次数≤60(2^60>1e18),剩余部分循环次数≤60,整体时间复杂度O(1),完全满足2秒时限。

六、总结

这道题的核心是**"避暴力、找规律、分段算"**:

  1. 利用二进制数的长度分布规律,把无限长的01串拆成若干段;
  2. 对每一段用数学公式批量统计1的数量,避免逐位遍历;
  3. 仅对最后不足整段的部分暴力补全,保证效率。

这种"分段统计+数学公式"的思路,是蓝桥杯大数问题的高频解法,掌握后能解决一类"无限序列统计"问题。建议大家动手跑一遍代码,修改不同的x值(比如x=10、x=1e5),加深对思路的理解。

相关推荐
历程里程碑2 小时前
LeetCode 283:原地移动零的优雅解法
java·c语言·开发语言·数据结构·c++·算法·leetcode
kupeThinkPoem2 小时前
std::thread的使用
c++
Eloudy2 小时前
通过示例看 C++ 函数对象、仿函数、operator( )
开发语言·c++·算法
superman超哥2 小时前
仓颉高性能实践:内存布局优化技巧深度解析
c语言·开发语言·c++·python·仓颉
Q741_1472 小时前
Linux UDP 服务端 实战思路 C++ 套接字 源码包含客户端与服务端 游戏服务端开发基础
linux·服务器·c++·游戏·udp
游戏23人生2 小时前
c++ 语言教程——17面向对象设计模式(六)
开发语言·c++·设计模式
superman超哥2 小时前
仓颉内存管理内功:栈与堆的分配策略深度解析
c语言·开发语言·c++·python·仓颉
ALex_zry2 小时前
C++中的“虚“机制解析:虚函数、纯虚函数与虚基类
c++
加成BUFF2 小时前
C++入门讲解6:数据的共享与保护核心机制解析与实践
开发语言·c++