UVa 12670 Counting Ones
题目描述
给定两个整数 AAA 和 BBB(1≤A≤B≤10161 \leq A \leq B \leq 10^{16}1≤A≤B≤1016),要求计算从 AAA 到 BBB(包含两端)的所有整数的二进制表示中,数字 111 出现的总次数。
例如:
- 输入:222 121212
- 输出:212121
数据范围:AAA 和 BBB 最大可达 101610^{16}1016,因此不能直接遍历统计,需要高效的数学方法。
题目分析
这是一个典型的数位统计问题。由于数据范围非常大,直接遍历每个数并统计其二进制中 111 的个数是不可行的(时间复杂度为 O(B−A)O(B-A)O(B−A),会超时)。
我们需要找到一种方法,能够快速计算从 111 到 nnn 的所有数的二进制中 111 的个数总和。
解题思路
关键思路
定义函数:
countOnes(n)=从 1 到 n 的所有数的二进制表示中 1 的个数总和
\text{countOnes}(n) = \text{从 1 到 n 的所有数的二进制表示中 1 的个数总和}
countOnes(n)=从 1 到 n 的所有数的二进制表示中 1 的个数总和
那么答案就是:
答案=countOnes(B)−countOnes(A−1)
\text{答案} = \text{countOnes}(B) - \text{countOnes}(A-1)
答案=countOnes(B)−countOnes(A−1)
如何计算 countOnes(n)\text{countOnes}(n)countOnes(n)
我们按二进制位分别计算每一位上 111 出现的次数,然后求和。
对于第 kkk 位(从最低位 000 开始):
- 该位的循环周期是 2k+12^{k+1}2k+1(即 000 和 111 交替的周期长度)
- 每个完整周期中,该位为 111 的次数是 2k2^k2k
- 设完整周期数为 ⌊n+12k+1⌋\lfloor \frac{n+1}{2^{k+1}} \rfloor⌊2k+1n+1⌋,那么完整周期贡献的 111 的个数为:
完整周期贡献=⌊n+12k+1⌋×2k \text{完整周期贡献} = \left\lfloor \frac{n+1}{2^{k+1}} \right\rfloor \times 2^k 完整周期贡献=⌊2k+1n+1⌋×2k - 剩余部分(不完整的周期):
剩余长度 r=(n+1) mod 2k+1r = (n+1) \bmod 2^{k+1}r=(n+1)mod2k+1
如果 r>2kr > 2^kr>2k,则多出的 111 的个数为 r−2kr - 2^kr−2k,否则为 000
因此:
第 k 位贡献=⌊n+12k+1⌋×2k+max(0,(n+1) mod 2k+1−2k)
\text{第 k 位贡献} = \left\lfloor \frac{n+1}{2^{k+1}} \right\rfloor \times 2^k + \max(0, (n+1) \bmod 2^{k+1} - 2^k)
第 k 位贡献=⌊2k+1n+1⌋×2k+max(0,(n+1)mod2k+1−2k)
对所有位 kkk 从 000 到 ⌊log2n⌋\lfloor \log_2 n \rfloor⌊log2n⌋ 求和,就是 countOnes(n)\text{countOnes}(n)countOnes(n)。
参考代码
// Counting Ones
// UVa ID: 12670
// Verdict: Accepted
// Submission Date: 2025-10-31
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net#include <iostream>using namespace std;typedef unsigned long long ull;// 计算从 1 到 n 的所有数的二进制中 1 的个数总和
ull countOnes(ull n) {ull total = 0; // 存储总个数ull currentPower = 1; // 2^kull nextPower = 2; // 2^(k+1)// 遍历每一位while (currentPower <= n) {// 完整周期的贡献total += ((n + 1) / nextPower) * currentPower;// 不完整周期的贡献ull remainder = (n + 1) % nextPower;if (remainder > currentPower) {total += remainder - currentPower;}// 移动到下一位currentPower <<= 1;nextPower <<= 1;}return total;
}int main() {ull A, B;// 处理多组测试用例while (cin >> A >> B) {// 计算 [A, B] 区间的 1 的个数 = countOnes(B) - countOnes(A-1)ull result = countOnes(B) - countOnes(A - 1);cout << result << endl;}return 0;
}
