【学习笔记】子集DP

背景

有一类问题和子集有关。

给你一个集合 S S S,令 T T T 为 S S S 的超集,也就是 S S S 所有子集的集合,求 T T T 中所有元素的和。

暴力1

先预处理子集的元素和 A i A_i Ai,再枚举子集。

cpp 复制代码
for(int s=0; s<(1<<n); s++)
	for(int i=0; i<(1<<n); i++)
		if(s&i) 
			f[s]+=A[i];

时间复杂度 O ( n 4 ) O(n^4) O(n4)

暴力2

其实枚举子集有个小 trick

cpp 复制代码
for(int s=0; s<(1<<n); s++)
	for(int i=s; i>0; i=(i-1)&s)
			f[s]+=A[i];

由二项式定理,时间复杂度为 O ( 3 n ) O(3^n) O(3n)

子集DP

令 g s , i g_{s,i} gs,i 为状态为 s s s,只考虑后 i i i 位转移的答案。

那么转移就是

g s , i = { g s , i − 1 s & ( 1 < < i ) = 0 g s , i − 1 + g s ⨁ ( 1 < < i ) , i − 1 o t h e r w i s e g_{s,i} = \begin{cases} g_{s,i-1} & s\&(1<<i)=0 \\g_{s,i-1} +g_{s\bigoplus(1<<i),i-1}&otherwise\end{cases} gs,i={gs,i−1gs,i−1+gs⨁(1<<i),i−1s&(1<<i)=0otherwise

这样可以做到不重不漏的转移。

推荐一篇blog,非常形象:https://www.cnblogs.com/maple276/p/17975253

可以发现,这个转移只和上一层有关,所以第二维是可以省略的。

前缀和(子集向超集转移)
cpp 复制代码
for(int i=0; i<n; i++)
{
	for(int s=0; s<(1<<n); s++)
	{
		if(s&_2)
			(f[s]+=f[s^_2])%(mod-1);
	}
	_2<<=1;
}
后缀和(超集向子集转移)
cpp 复制代码
for(int i=0; i<n; i++)
{
	for(int s=0; s<(1<<n); s++)
	{
		if(!(s&_2))
			(f[s]+=f[s^_2])%(mod-1);
	}
	_2<<=1;
}
差分
cpp 复制代码
//后缀差分
for(int i=0; i<20; i++)
{
	for(int s=0; s<S; s++)
	{
		if(!(s&_2))
			(f[s]-=f[s^_2])%=mod;
	}
	_2<<=1;
}

例题

CF165E

定义 x x x 和 y y y 相容为 x & y = 0 x\&y=0 x&y=0,给你一个序列 A A A,对于每个元素,在 A A A 中找到和它相容的元素。 ∣ A ∣ ≤ 1 0 6 , A i ≤ 4 × 1 0 6 |A|\leq 10^6,A_i\leq 4\times 10^6 ∣A∣≤106,Ai≤4×106

思路

x & y = 0 x\&y=0 x&y=0 等价于 x ˜ & y = y \~x\&y=y x˜&y=y,那么只需要对 A A A做类似前缀和的操作,加法改成覆盖即可。

代码
cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int S=1<<22;
void O_o()
{
	int n;
	cin>>n;
	vector<int> f(S,-1),a(n+1);
	for(int i=1; i<=n; i++)
	{
		cin>>a[i];
		f[a[i]]=a[i];
	}
	int _2=1;
	for(int i=0; i<=21; i++)
	{
		for(int s=0; s<S; s++)
		{
			if(!(s&_2)) continue;
			if(f[s^_2]!=-1)
				f[s]=f[s^_2];
		}
		_2<<=1;
	}
	int t=S-1;
	for(int i=1; i<=n; i++)
	{
		cout<<f[t^a[i]]<<" ";
	}
	cout<<"\n";
}
signed main()
{
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
	cout<<fixed<<setprecision(2);
	int T=1;
//	cin>>T;
	while(T--)
	{
		O_o();
	}
}
相关推荐
冻柠檬飞冰走茶20 分钟前
字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)
c语言·开发语言·数据结构·算法·动态规划
橙橙笔记37 分钟前
具身智能的学习 - 完成一个机械臂 第1节
python·学习·机械臂·具身智能
雪落漂泊40 分钟前
C++算法 二分查找篇(上)
开发语言·c++·算法
成都蜀味缘餐饮技术培训学校1 小时前
手撕烤兔技术培训实战指南,秘制腌料配方比例与调制手法
大数据·经验分享·笔记·其他·微信公众平台
希和20272 小时前
免费AI视频生成工具汇总(国内优先,适合做漫剧、短视频,Obsidian笔记版)
人工智能·笔记·音视频
LabVIEW开发2 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
蒸蒸yyyyzwd3 小时前
秋招学习笔记 day109
笔记·八股
严肃小白兔4 小时前
MATLAB 数值计算中的精度与误差问题:浮点运算、误差传播与数值稳定性
人工智能·算法·matlab
Hhy_11074 小时前
《C++深度解构06》手撕 string —— 从底层内存管理到深浅拷贝彻底吃透
c语言·开发语言·c++·学习·visual studio
青少儿编程课堂5 小时前
蓝桥杯青少组图形化省考报名倒计时·全国青少年信息素养大赛·电子学会图形化考级:校园黑白棋人机对战解析
c++·python·算法·bfs·信息学竞赛