【学习笔记】高维前缀和(SOSDP)

高维前缀和(SOSDP)解决这样的问题:

给定 \(f_i\),其中 \(i\in[0,2^n-1]\),求解 \(\sum\limits_{j\subseteq i}f_j\)

考虑一维前缀和:

for(int i=1;i<=n;i++){
	sum[i]=sum[i-1]+a[i];
}

二维前缀和:

for(int i=1;i<=n;i++){
	for(int j=1;j<=n;j++){
		sum[i][j]=sum[i][j-1]+a[i][j];
	}
}
for(int i=1;i<=n;i++){
	for(int j=1;j<=n;j++){
		sum[i][j]+=sum[i-1][j];
	}
}

三维前缀和:

for(int i=1;i<=n;i++){
	for(int j=1;j<=n;j++){
		for(int k=1;k<=n;k++){
			sum[i][j][k]=sum[i][j][k-1]+a[i][j][k];
		}
	}
}
for(int i=1;i<=n;i++){
	for(int j=1;j<=n;j++){
		for(int k=1;k<=n;k++){
			sum[i][j][k]+=sum[i][j-1][k];
		}
	}
}
for(int i=1;i<=n;i++){
	for(int j=1;j<=n;j++){
		for(int k=1;k<=n;k++){
			sum[i][j][k]+=sum[i-1][j][k];
		}
	}
}

可以发现上面那个问题就是每一维大小都为 \(2\)\(n\) 维前缀和。
因此可以考虑枚举每一维,然后再加上这一维的贡献就好了。
这里分为两种DP:子集和超集。
子集就是指子集。

for(int i=1;i<=n;i++){
	for(int S=0;S<1<<n;S++){
		if(S>>(i-1)&1){
			continue;
		}
		f[S|1<<(i-1)]+=f[S];
	}
}

自己是超集的子集。
换句话说,上面的问题的判断条件为 \(i\subseteq j\) 时应使用超集。
因为是用大的更新小的,所以 \(S\) 要倒序枚举。
Upd on 2024.12.27:似乎并不用倒序枚举,因为对于每一维,每个 \(S\) 要么只增加要么只被增加,而且只会用一遍。因此不用考虑转移顺序。后面一道题的高维差分也是一样的。

for(int i=1;i<=n;i++){
	for(int S=(1<<n)-1;~S;S--){
		if(S>>(i-1)&1){
			f[S^1<<(i-1)]+=f[S];
		}
	}
}

CF 1234F
因为字符不重复,因此不用考虑它们的排列顺序,即翻转子串就是将两个不交的子串连到一块。这里不交既指位置不交又指字符集不交。但显然字符集不交则一定位置不交。因此只用考虑对每一个字符集处理最大长度。高维前缀最大值即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5,maxm=(1<<20)+5;
int n,dp[maxm];
char s[maxn];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	scanf(" %s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++){
		for(int j=i,S=0;j;j--){
			if(S>>(s[j]-'a')&1){
				break;
			}
			S|=1<<(s[j]-'a');
			dp[S]=i-j+1;
		}
	}
	for(int i=1;i<=20;i++){
		for(int S=0;S<1<<20;S++){
			if(S>>(i-1)&1){
				continue;
			}
			dp[S|1<<(i-1)]=max(dp[S|1<<(i-1)],dp[S]);
		}
	}
	int ans=0;
	for(int S=1;S<=(1<<20)-2;S++){
		ans=max(ans,dp[S]+dp[((1<<20)-1)^S]);
	}
	printf("%d",ans);
	return 0;
}
}
int main(){return asbt::main();}

CF 165E
\(a_i\le 4\times 10^6\),也就是说 \(a\) 的全集为 \(2^{22}\)。考虑答案就是 \(a_i\) 的补集的某个子集。SOSDP将 \(a\) 值不断上传即可。注意代码中选择了取 \(\max\),目的是避免被 \(0\) 更新。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5,maxm=(1<<22)+5;
int n,a[maxn],f[maxm];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(a[i]);
		f[a[i]]=a[i];
	}
	for(int i=1;i<=22;i++){
		for(int S=0;S<1<<22;S++){
			if(S>>(i-1)&1){
				continue;
			}
			int nS=S|1<<(i-1);
			f[nS]=max(f[nS],f[S]);
		}
	}
	for(int i=1;i<=n;i++){
		int tmp=((1<<22)-1)^a[i];
		printf("%d ",f[tmp]?f[tmp]:-1);
	}
	return 0;
}
}
int main(){return asbt::main();}

AT arc100C
考虑对每个 \(k\),算出 \(i\mid j\subseteq k\) 的答案,然后再对 \(k\) 进行前缀最大值即可。考虑若 \(i\mid j\subseteq k\),则一定满足 \(i\subseteq k\)\(j\subseteq k\)。因此高维前缀最大值、次大值即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<18)+5;
int n,f[maxn],g[maxn],hp[7],ans[maxn];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	for(int i=0;i<1<<n;i++){
		read(f[i]);
	}
	for(int i=1;i<=n;i++){
		for(int S=0;S<1<<n;S++){
			if(S>>(i-1)&1){
				continue;
			}
			int nS=S|1<<(i-1);
			hp[1]=f[S],hp[2]=g[S];
			hp[3]=f[nS],hp[4]=g[nS];
			sort(hp+1,hp+5);
			f[nS]=hp[4],g[nS]=hp[3];
		}
	}
	for(int i=1;i<1<<n;i++){
		ans[i]=max(ans[i-1],f[i]+g[i]);
		printf("%d\n",ans[i]);
	}
	return 0;
}
}
int main(){return asbt::main();}

CF 1208F
首先,\(a_i\mid(a_j\& a_k)=((\complement_Ua_i)\&a_j\&a_k)+a_i\)
二进制数最大,我们考虑按位贪心。枚举到 \(i\) 时,若要这一位为 \(1\),则必须满足至少有两个 \(a\) 值的这一位为 \(1\),且下标必须大于 \(i\)。SOSDP维护超集的最大与次大位置即可。
\(i\) 的枚举只能到 \(n-2\),因为 \(i=n-1\)\(n\)\(((\complement_Ua_i)\&a_j\&a_k)\) 这一部分会被算成 \(0\),答案会被 \(a_i\) 直接更新。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5;
const int maxm=(1<<21)+5,uS=(1<<21)-1;
int n,a[maxn],f[maxm],g[maxm],hp[10];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	for(int i=1;i<=n;i++){
		read(a[i]);
		hp[1]=f[a[i]];
		hp[2]=g[a[i]];
		hp[3]=i;
		sort(hp+1,hp+4);
		f[a[i]]=hp[3];
		g[a[i]]=hp[2];
	}
	for(int i=1;i<=21;i++){
		for(int S=uS;~S;S--){
			if(S>>(i-1)&1){
				int nS=S^1<<(i-1);
				hp[1]=f[S],hp[2]=g[S];
				hp[3]=f[nS],hp[4]=g[nS];
				sort(hp+1,hp+5);
				f[nS]=hp[4],g[nS]=hp[3];
			}
		}
	}
//	for(int i=0;i<=15;i++){
//		cout<<f[i]<<" "<<g[i]<<"\n";
//	}
	int ans=0;
	for(int i=1;i<=n-2;i++){
		int nS=uS^a[i],res=0;
//		cout<<i<<"\n";
		for(int j=21;~j;j--){
//			cout<<" "<<j<<"\n";
			if(nS>>j&1){
				res|=1<<j;
				if(g[res]<=i){
					res^=1<<j;
				}
			}
		}
		ans=max(ans,a[i]+res);
	}
	printf("%d",ans);
	return 0;
}
}
int main(){return asbt::main();}
/*
3
1 5 15
*/

CF 383E
考虑单词不合法,即每一个字母都不是元音。
对每个单词状压,然后高维前缀和计算每一个字符集包含的单词数量,最后枚举元音的补集统计答案。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<24)+5;
int n,f[maxn];
char s[10];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	for(int i=1,S;i<=n;i++){
		scanf(" %s",s+1);
		S=0;
		for(int j=1;j<=3;j++){
			S|=1<<(s[j]-'a');
		}
		f[S]++;
	}
	for(int i=1;i<=24;i++){
		for(int S=0,nS;S<1<<24;S++){
			if(S>>(i-1)&1){
				continue;
			}
			nS=S|1<<(i-1);
			f[nS]+=f[S];
		}
	}
	int ans=0;
	for(int i=0;i<1<<24;i++){
		ans^=(n-f[i])*(n-f[i]);
	}
	printf("%d",ans);
	return 0;
}
}
int main(){return asbt::main();}

CodeChef COVERING
\(f_S=\sum\limits_{i\mid j\mid k=S}a_ib_jc_k\),则不难发现答案即为 \(\sum f_S\times 2^{|S|}\)
考虑如何求解 \(f\),不难想到令 \(A_S\)\(B_S\)\(C_S\)\(\sum\limits_{i\subseteq S}a_i\)\(\sum\limits_{i\subseteq S}b_i\)\(\sum\limits_{i\subseteq S}c_i\),则有:

\[f_S=A_SB_SC_S-\sum_{i\subsetneqq S}f_i \]

移项得:

\[A_SB_SC_S=\sum_{i\subseteq S}f_i \]

高维差分即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define popcnt __builtin_popcount
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=(1<<20)+5,mod=1e9+7;
int n,f[maxn];
int a[maxn],b[maxn],c[maxn];
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	for(int i=0;i<1<<n;i++){
		read(a[i]);
	}
	for(int i=0;i<1<<n;i++){
		read(b[i]);
	}
	for(int i=0;i<1<<n;i++){
		read(c[i]);
	}
	for(int i=1;i<=n;i++){
		for(int S=0,nS;S<1<<n;S++){
			if(S>>(i-1)&1){
				continue;
			}
			nS=S|1<<(i-1);
			(a[nS]+=a[S])%=mod;
			(b[nS]+=b[S])%=mod;
			(c[nS]+=c[S])%=mod;
		}
	}
	for(int i=0;i<1<<n;i++){
		f[i]=a[i]*1ll*b[i]%mod*c[i]%mod;
	}
	for(int i=1;i<=n;i++){
		for(int S=0;S<1<<n;S++){
			if(S>>(i-1)&1){
				continue;
			}
			(f[S|1<<(i-1)]+=mod-f[S])%=mod;
		}
	}
	int ans=0;
	for(int i=0;i<1<<n;i++){
		(ans+=f[i]*(1ll<<popcnt(i))%mod)%=mod;
	}
	printf("%d",ans);
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2024-12-22 22:02  zhangxy__hp  阅读(118)  评论(0)    收藏  举报