SOSDP(子集DP/高维前缀和dp)

1,定义:

sosdp一般是利用前缀和的思想来子集和相关的问题的。

2,思路:

1,先谈谈前缀和

  1. 我们前缀和一般是用容斥原理表示,但是也有另外一种方法,就是保持其他维不变,每次只求一维的和。
  2. 以二维为例子。
  3. 如果我们求蓝色区域前缀和,我们可以先把每层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. 异或其实就是不进位的加法。我们试图让左右相等,只需要保证二进制相加时,每一位不同时为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;
}

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值