洛谷 AT2272 [ARC066B] Xor Sum 蓝 题解
结论
-
\(a+b = a \oplus b + (a \operatorname{and} b) << 1\)
-
令 \(f[j]\) 表示 \(a+b=j\) 的方案数,那么状态转移方程为 \(f[j] = f[\frac{j}{2}] + f[\frac{j-1}{2}] + f[\frac{j-2}{2}]\),答案为 \(f[n]\).
理论基础
第一条结论
异或相当于不进位加法,在异或的基础上,考虑进位:对于 \(a,b\) 在二进制下的每一位 \(a_i, b_i\),\((a_i \operatorname{and} b_i) = 1\) 必然等价于 \(a_i = b_i = 1\),那么这一位相加必然要进位,也就是左移一位。所以 \(a+b = a \oplus b + (a \operatorname{and} b) << 1\) (感觉不是很严谨)。
状态定义
为什么要令 \(f[j]\) 表示 \(a+b=j\) 的方案数?
由第一条结论可以得到,\(a \oplus b \le a + b\)。
我们令 \(u = a \oplus b,\ v = a + b\),那么满足 \(u \le v\),我们只需对 \(v\) 进行 DP,求方案数即可。
状态转移方程
这一步是重点,在此再提醒一下,\(f[j]\) 表示 \(a+b=j\) 对应的方案数!
我们令 \((x, y)_i\) 表示 \(u,v\) 二进制下的第 \(i\) 位数字填什么,那么只可能有三种情况:\((0,0)_i, (0,1)_i, (1,1)_i\)。因为要满足 \(u \le v\),所以 \((1,0)_i\) 不合法。
接下来我们举一个例子来理解状态转移方程
| 填数 | 二进制 \(a\) | 二进制 \(b\) | 十进制 \(a\) | 十进制 \(b\) | 十进制 \(a+b\) |
|---|---|---|---|---|---|
| 原数 | \(1101\) | \(1001\) | \(13\) | \(9\) | \(22\) |
| 填 \((0,0)\) | \(11010\) | \(10010\) | \(26\) | \(18\) | \(44\) |
| 填 \((0,1)\) | \(11010\) | \(10011\) | \(26\) | \(19\) | \(45\) |
| 填 \((1,1)\) | \(11011\) | \(10011\) | \(27\) | \(19\) | \(46\) |
你会发现原数 \(j\) 可以转移到 \(2j,2j+1,2j+2\),那么状态转移方程就为:\(f[j] = f[\frac{j}{2}] + f[\frac{j-1}{2}] + f[\frac{j-2}{2}]\)
初始化:\(f[0]=1,f[1]=2\),手算即可。
小细节
\(N \le 10^{18}\) 存不下,可以用 \(\operatorname{map}\) 来实现。
Code
#include <iostream>
#include <map>
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL n, ans;
map<LL, LL> f;
LL clac(LL n)
{
if (f[n] != 0) return f[n];
return f[n] = (clac(n / 2) + clac((n - 1) / 2) + clac((n - 2) / 2)) % mod;
}
int main()
{
cin >> n;
f[0] = 1;
f[1] = 2;
ans = clac(n);
cout << ans << endl;
return 0;
}
完结撒花!

具有思维含量的数学+DP
浙公网安备 33010602011771号