1,定义:
sosdp一般是利用前缀和的思想来子集和相关的问题的。
2,思路:
1,先谈谈前缀和
- 我们前缀和一般是用容斥原理表示,但是也有另外一种方法,就是保持其他维不变,每次只求一维的和。
- 以二维为例子。
- 如果我们求蓝色区域前缀和,我们可以先把每层y的绿色区域x的前缀和先算起来。然后在y轴时,此时我们的dp[x][y]表示的是第y层,x轴上个x个前缀和为dp[x][y],那么把这y层加起来,就变成了整个x,y区域的前缀和了。
同理,我们可以通过一维一维的方式求高维前缀和(鬼才这么用,容斥不香吗!)
最底层是10110的所有子集,我们通过按二进制位枚举来更新,最终可以以nlogn来获得所有子集的前缀和
2,但是,当每一维都只有2个元素(可以用0,1表示了呀,那么二进制的每一位就是一维,每一位的0,1就是该维d)。不就可以状态压缩成二进制来求取。所以一般可以用于求取子集和问题。
如对于10011的子集和(前缀和)(1表示存在该元素)可以表示为f(10011)=f(10010)+f(10001)+f(00011),每次保留1位不同不同,其余相同。逐位处理。
for (int i = 0; i < n; ++i)for (int j = 0; j < (1 << n); ++j)
{
if ((j >> i) & 1)dp[j] += dp[j ^ (1 << i)];//注意,是i位逐位处理,每次处理所有数(即整个区间)
}
3,例题:G A Xor B Problem again
思路:
- 异或其实就是不进位的加法。我们试图让左右相等,只需要保证二进制相加时,每一位不同时为1即可,换句话说,求与x匹配的数,即用全1去异或x得到tmp,然后求tmp的子集和即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
typedef unsigned long long ull;
typedef pair<long long, long long> pll;
typedef pair<int, int> pii;
int dp[1 << 17], a[1 << 17];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)cin >> a[i], dp[a[i]]++;//先记录初始个数
for (int i = 0; i < 17; ++i)for (int j = 0; j < (1 << 17); ++j)if ((j >> i) & 1)dp[j] += dp[j ^ (1 << i)];//逐位遍历更新
ll ans = 0;
for (int i = 1; i <= n; ++i)ans += dp[(1<<17)-1-a[i]];//1<<17-1,把17位(0~16)都置为1
cout << ans << endl;
return 0;
}