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

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

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

考虑一维前缀和:

复制代码
  • 1
  • 2
  • 3
for(int i=1;i<=n;i++){ sum[i]=sum[i-1]+a[i]; }

二维前缀和:

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
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]; } }

三维前缀和:

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
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:子集和超集。
子集就是指子集。

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
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\) 要么只增加要么只被增加,而且只会用一遍。因此不用考虑转移顺序。后面一道题的高维差分也是一样的。

复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
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
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
#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
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
#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
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
#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
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
#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
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
#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
复制代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
#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 @   zhangxy__hp  阅读(28)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开