题解:P14074 [GESP202509 五级] 有趣的数字和
洛谷 P14074 题解
解题思路
大多数人都会选择枚举 \([l,r]\) 之间的整数,看是否是有趣的,但是肯定会超时
求 \([l,r]\) 之间有趣数字和十分困难,但是求 \([1,l]\) 之间的有趣数字和却能发现规律
我们可以根据一些例子找规律,如:\({8,9,10,11}\)
二进制形式分别为 \({1000, 1001, 1010, 1011}\)
我们发现数字前面都有“10”,数字和是1(奇数),那么 \({8, 11}\) 是有趣的数字
否则就是 \({9, 10}\)
我们多举几个例子不难发现,\({4x,4x+1,4x+2,4x+3}\) 几个数字,高位肯定有一段相同的地方,如果那段地方数字和是奇数,则 \({4x,4x+3}\) 是有趣的数字,否则另两个数字是有趣的
所以,\({4x,4x+1,4x+2,4x+3}\) 之间的有趣数字之和一定是 \(8x+3\),我们就可以用这个规律计算出 \([1, 4k+3]\) 之间好数字之和
\[8 \times \frac{(k-1)\times k}{2} = 4k^2 - k
\]
接着枚举其他没有计算到的数(绝对不会超过4个)即可
不要忘了,我们求的是以1为开头的,所以我们可以设一个函数 \(f(n)\) 表示 \([1,n]\) 之间的有趣数字和
最后答案等于 \(f(r)-f(l-1)\),类似前缀和的思路
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int s[16] = {0, 1, 3, 3, 7, 7, 7, 14, 22, 22, 22, 33, 33, 46, 60, 60}; //打表出省一
inline int sum(int n) //计算一个数的二进制位数位之和
{
if (n == 1)
{
return 1;
}
int cnt = 0; //数位之和
for (int i = 0; i <= 30; i++) //30位以内
{
if ((n >> i) & 1)
{
cnt++;
}
}
return cnt;
}
inline int f(int n) //计算[1,n]的有趣数字和
{
if (n < 16)
{
return s[n]; //可爱的打表
}
int b = ((n - 4) >> 2); //b为计算完整区间的数量相关参数
int a = ((b * b) << 2); //a为最终的答案(初始值,完整区间的和)
a -= b;
for (int i = (b << 2); i <= n; i++)
{
if (sum(i) & 1) //二进制数字和为奇数
{
a += i; //加上i
}
}
return a;
}
signed main()
{
int l, r;
cin >> l >> r; //输入
cout << f(r) - f(l - 1) << endl; //输出
return 0;
}