洛谷 AT2272 [ARC066B] Xor Sum 蓝 题解

结论

  1. \(a+b = a \oplus b + (a \operatorname{and} b) << 1\)

  2. \(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;
}

完结撒花!

posted @ 2022-09-13 09:12  LittleMoMol  阅读(29)  评论(0编辑  收藏  举报
*/