第十六届蓝桥杯大赛软件赛省赛Java 研究生组F题
·
题解:01 串
源题目地址:https://www.luogu.com.cn/problem/P12191
题目分析
题目要求计算由数字0,1,2,…的二进制表示拼接而成的无限01串的前x位中1的个数。例如前7位是"0110111",包含5个1。
解题思路
-
理解字符串构造方式:字符串由数字的二进制表示按顺序拼接而成。数字0对应"0",1对应"1",2对应"10",3对应"11",依此类推。
-
分段处理:我们可以将数字按二进制位数分组处理:
• 1位数字:0,1
• 2位数字:2,3
• 3位数字:4-7
• 以此类推 -
计算每段的贡献:
• 对于完整的l位数字段,可以快速计算其总1的个数
• 对于不完整的段,需要单独处理部分数字或部分位 -
关键公式:
•F(n)函数计算0到n所有数字的二进制1的总和
• 对于l位数字段,总1的个数为2^(l-1) + (l-1)*2^(l-2)
代码实现
import java.util.Scanner;
public class Main {
// 计算 F(n) = sum_{i=0}^{n} popcount(i)
static long F(long n) {
if (n == 0)
return 0;
// 找到 n 的二进制最高位 p, 使得 2^p <= n
int p = 63 - Long.numberOfLeadingZeros(n);
long power = 1L << p; // 2^p
long rem = n - power;
// 公式:F(n) = p*2^(p-1) + (n - 2^p + 1) + F(n - 2^p)
return (long) p * (power >> 1) + (rem + 1) + F(rem);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long x = sc.nextLong();
long ones = 0; // 累计 1 的个数
long bitsUsed = 0; // 已经统计的位数
// 先处理数字 0 ("0") —— 长度 1,0 个 1
if (x > 0) {
bitsUsed++; // 用去1位
// 如果 x 仅为1,则答案为 0
if (bitsUsed == x) {
System.out.println(0);
return;
}
}
// 接下来处理 l 位数字(l>=1)
int l = 1;
long groupStart = 1; // l位数字的起始值(l=1时仅有1)
while (true) {
long countNumbers = 1L << (l - 1); // 数字个数 = 2^(l-1)
long segmentBits = countNumbers * (long) l; // 这一段总位数
if (bitsUsed + segmentBits <= x) {
// 整段都被计入
// 对于 l位数字,其总 1 数按已知公式为:
// 对 l = 1,只有数字 1,本身贡献 1;
// 对 l>=2,总和为: 2^(l-1) + (l-1)*2^(l-2)
long sumOnesGroup;
if (l == 1) {
sumOnesGroup = 1;
} else {
sumOnesGroup = (1L << (l - 1)) + (l - 1) * (1L << (l - 2));
}
ones += sumOnesGroup;
bitsUsed += segmentBits;
l++;
groupStart <<= 1; // 下个分段起点乘以2
} else {
// 本分段仅部分数字被用到
long remain = x - bitsUsed; // 当前分段内剩余的位数
// 完整的数字个数:
long fullNumbers = remain / l;
if (fullNumbers > 0) {
// 计算区间 [groupStart, groupStart + fullNumbers - 1] 内 1 的和
long a = groupStart;
long b = groupStart + fullNumbers - 1;
long sumOnesPart = F(b) - F(a - 1); // a>=1,此时 F(a-1)计算正确(F(0)=0)
ones += sumOnesPart;
bitsUsed += fullNumbers * l;
}
// 处理下一个数字的部分位:如果还余下 partial 位
long partial = remain % l;
if (partial > 0) {
long nextNumber = groupStart + fullNumbers;
// 此数字保证有 l 位(二进制字符串的长度为 l)
// 取其前 partial 位(从最高位开始),统计其中 1 的数目
for (int i = 0; i < partial; i++) {
// 取第 i 位:从最高位开始,对应下标 l - i - 1(0 表示最低位)
if (((nextNumber >> (l - i - 1)) & 1) == 1) {
ones++;
}
}
}
break;
}
}
System.out.println(ones);
}
}
复杂度分析
• 时间复杂度:O(log²x),因为F(n)函数是递归实现,每次递归处理n的最高位,而主循环也是按数字的位数递增处理。
• 空间复杂度:O(logx),递归深度与n的二进制位数成正比。
该算法高效地处理了极大范围的x值(直到10^18),通过分段处理和递归计算避免了暴力枚举的低效问题。
更多推荐


所有评论(0)