题解:洛谷 P14074 有趣的数字和
【题目来源】
洛谷:P14074 [GESP202509 五级] 有趣的数字和 - 洛谷
【题目描述】
如果一个正整数的二进制表示包含奇数个 \(1\),那么小 A 就会认为这个正整数是有趣的。
例如,\(7\) 的二进制表示为 \((111)_2\),包含 \(1\) 的个数为 \(3\) 个,所以 \(7\) 是有趣的。但是 \(9=(1001)_2\) 包含 \(2\) 个 \(1\),所以 \(9\) 不是有趣的。
给定正整数 \(l,r\),请你统计满足 \(l≤n≤r\) 的有趣的整数 \(n\) 之和。
【输入】
一行,两个正整数 \(l,r\),表示给定的正整数。
【输出】
一行,一个正整数,表示 \(l,r\) 之间有趣的整数之和。
【输入样例】
3 8
【输出样例】
19
【核心思想】
-
问题分析:给定正整数 \(l, r\)(\(1 \le l \le r \le 10^9\)),需要统计区间 \([l, r]\) 内所有“有趣”的整数之和。“有趣”定义为正整数的二进制表示中 \(1\) 的个数为奇数。直接枚举区间内每个数并检查二进制中 \(1\) 的个数会超时,需要利用二进制表示的周期性规律将区间求和转化为前缀和,实现 \(O(\log n)\) 级别的计算。
-
算法选择:
- 前缀和 + 数学规律:定义 \(S(x)\) 为 \([1, x]\) 内所有有趣整数之和,则答案为 \(S(r) - S(l-1)\)。核心在于高效计算 \(S(x)\)。
- 关键规律:每连续四个数 \(4n, 4n+1, 4n+2, 4n+3\) 构成一组,该组内有趣数之和恒为 \(8n+3\)。
- 证明:设 \(4n\) 的高位部分(即 \(n\))中 \(1\) 的个数为 \(p\)。这四个数的二进制末尾两位分别为
00、01、10、11。若 \(p\) 为偶数,有趣数为 \(4n+1\) 和 \(4n+2\),和为 \((4n+1)+(4n+2)=8n+3\);若 \(p\) 为奇数,有趣数为 \(4n\) 和 \(4n+3\),和为 \(4n+(4n+3)=8n+3\)。因此无论 \(p\) 的奇偶性如何,每组和恒为 \(8n+3\)。
- 证明:设 \(4n\) 的高位部分(即 \(n\))中 \(1\) 的个数为 \(p\)。这四个数的二进制末尾两位分别为
-
关键步骤:
- 读入:读取 \(l, r\)。
- 定义辅助函数
check(x):计算正整数 \(x\) 的二进制表示中 \(1\) 的个数,若为奇数则返回true。 - 定义前缀和函数
preSum(x):- 若 \(x \le 0\),返回 \(0\)。
- 令 \(n = \lfloor x/4 \rfloor\),表示完整组的数量。
- 完整组贡献:\(\sum_{i=0}^{n-1} (8i+3) = 4n^2 - n\)。
- 剩余部分:遍历 \(i\) 从 \(4n\) 到 \(x\),若
check(i)为真则累加 \(i\)。 - 返回完整组贡献与剩余部分贡献之和。
- 输出答案:
preSum(r) - preSum(l-1)。
-
时间/空间复杂度:
- 时间复杂度:\(O(\log r)\),因为 \(n = x/4\) 的计算是 \(O(1)\),剩余部分最多遍历 \(3\) 个数(\(x\) 到 \(4n\) 之间最多 \(3\) 个数),而
check函数每次检查 \(O(\log x)\) 位,整体为常数时间。 - 空间复杂度:\(O(1)\)。
- 时间复杂度:\(O(\log r)\),因为 \(n = x/4\) 的计算是 \(O(1)\),剩余部分最多遍历 \(3\) 个数(\(x\) 到 \(4n\) 之间最多 \(3\) 个数),而
-
数学规律 + 前缀和的核心思想:
- 二进制周期性:连续四个整数的二进制表示具有固定的尾部模式(
00、01、10、11),这使得每组中有趣数的和与高位部分 \(n\) 的奇偶性无关,恒为 \(8n+3\)。 - 前缀和转化:利用前缀和将区间查询转化为两次前缀和计算,避免直接遍历区间。
- 分组计算:将 \([1, x]\) 划分为若干个完整组(每组4个数)和一个长度不超过3的剩余段,完整组用等差数列求和公式 \(O(1)\) 计算,剩余段暴力枚举(最多3个数)。
- 适用场景:适用于二进制位模式具有周期性规律、且需要处理大范围区间求和的问题。
- 二进制周期性:连续四个整数的二进制表示具有固定的尾部模式(
【算法标签】
普及 #前缀和
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用长整型防止溢出
int l, r; // 输入的数字区间[l, r]
int ans; // 存储计算结果
int b; // 计算第b个区间
int v[9] = {0, 1, 3, 3, 7, 7, 7, 14, 22}; // 预计算的小数值结果(1-8的答案)
/**
* 计算数字x的二进制表示中1的个数(汉明重量)
* @param x 要计算的数字
* @return 二进制中1的个数
*/
int check(int x)
{
int cnt = 0; // 计数器,记录1的个数
while (x > 0)
{
if (x & 1) // 检查最低位是否为1
cnt++; // 如果是1,计数器加1
x = x >> 1; // 右移一位,相当于除以2
}
return cnt;
}
/**
* 计算从1到x的所有满足条件的数的和
* @param x 上界
* @return 计算结果
*/
int f(int x)
{
// 如果x小于等于8,直接返回预计算的结果
if (x <= 8)
return v[x];
// 计算区间个数(默认向下取整)
b = x / 4;
// 计算从第1到第b个区间的总和
ans = (b * b) * 4 - b;
// 计算剩余部分的值(从b*4到x)
for (int i = (b * 4); i <= x; i++)
{
// 如果二进制1的个数为奇数,则累加
if (check(i) % 2)
ans += i;
}
return ans;
}
signed main()
{
// 输入区间范围
cin >> l >> r;
// 输出区间内满足条件的数的和(前缀和思想)
cout << f(r) - f(l - 1);
return 0;
}
【运行结果】
3 8
19
浙公网安备 33010602011771号