题解:洛谷 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

【核心思想】

  1. 问题分析:给定正整数 \(l, r\)\(1 \le l \le r \le 10^9\)),需要统计区间 \([l, r]\) 内所有“有趣”的整数之和。“有趣”定义为正整数的二进制表示中 \(1\) 的个数为奇数。直接枚举区间内每个数并检查二进制中 \(1\) 的个数会超时,需要利用二进制表示的周期性规律将区间求和转化为前缀和,实现 \(O(\log n)\) 级别的计算。

  2. 算法选择

    • 前缀和 + 数学规律:定义 \(S(x)\)\([1, x]\) 内所有有趣整数之和,则答案为 \(S(r) - S(l-1)\)。核心在于高效计算 \(S(x)\)
    • 关键规律:每连续四个数 \(4n, 4n+1, 4n+2, 4n+3\) 构成一组,该组内有趣数之和恒为 \(8n+3\)
      • 证明:设 \(4n\) 的高位部分(即 \(n\))中 \(1\) 的个数为 \(p\)。这四个数的二进制末尾两位分别为 00011011。若 \(p\) 为偶数,有趣数为 \(4n+1\)\(4n+2\),和为 \((4n+1)+(4n+2)=8n+3\);若 \(p\) 为奇数,有趣数为 \(4n\)\(4n+3\),和为 \(4n+(4n+3)=8n+3\)。因此无论 \(p\) 的奇偶性如何,每组和恒为 \(8n+3\)
  3. 关键步骤

    • 读入:读取 \(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)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(\log r)\),因为 \(n = x/4\) 的计算是 \(O(1)\),剩余部分最多遍历 \(3\) 个数(\(x\)\(4n\) 之间最多 \(3\) 个数),而 check 函数每次检查 \(O(\log x)\) 位,整体为常数时间。
    • 空间复杂度:\(O(1)\)
  5. 数学规律 + 前缀和的核心思想

    • 二进制周期性:连续四个整数的二进制表示具有固定的尾部模式(00011011),这使得每组中有趣数的和与高位部分 \(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
posted @ 2026-09-08 20:29  团爸讲算法  阅读(4)  评论(0)    收藏  举报