简单的求子集和的4种做法:
for(int i=0;i<(1<<n);i++) for(int j=0;j<i;j++) if(j&i==j)F[i]+=A[j];
for(int i=0;i<(1<<n);i++) { F[i]=A[0]; for(int j=i;j>0;j=(j-1)&i) F[i]+=A[j]; }
,这一做法用到了高位前缀和
for(int i=0;i<n;i++) for(int j=0;j<(1<<n);j++) if(j&(1<<i))F[j]+=F[j^(1<<i)];
,这一做法是fwt的一个特性
代码不给出了,就是做一遍or卷积,而方法3的做法和fmt差不多,也就是为什么有些人不写fwt写fmt的原因。
技巧:
如果我们现在有
我们要求i除去i的真子集后的值。我们只需倒着做一遍即可。
for(int i=N-1;i>=0;i--) { for(int j=0;j<(1<<N);j++) if(j&(1<<i))Add(F[j],mod-F[j^(1<<i)]); }
例题:
CF1208F
You are given an array a of n integers.
You need to find the maximum value of ai|(aj&ak) over all triplets (i,j,k) such that i<j<k.
有点难度,首先转化题目
然后就是对于每个ai进行考虑,因为是 and ,所以肯定是从高位到低位贪心。所以我们只要求出有多少数在第位上是1,且前位是。这个直接子集DP一波即可。
原先只要高位前缀和一下就行,但现在是每次加入一个数,那么就要麻烦一点(需要对子集DP有一定理解)。
#include<bits/stdc++.h> using namespace std; #define next Next #define gc getchar const int N=2e6+5; int n,ans,a[N],dp[N][21]; /*char buf[1<<21],*p1=buf,*p2=buf; inline int gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}*/ inline int read() { int ret=0,f=0;char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){ret=ret*10+c-48;c=gc();} if(f)return -ret;return ret; } void add(int x,int y) { if(y>20)return; if(dp[x][y]>1)return; dp[x][y]++; add(x,y+1); if(x&(1<<y))add(x^(1<<y),y); } signed main() { n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=n;i;i--) { int res=0,S=0; for(int j=20;j>=0;j--) if(a[i]&(1<<j))res+=(1<<j); else if(dp[S+(1<<j)][20]>1) { res+=(1<<j); S+=(1<<j); } add(a[i],0); if(i<=n-2)ans=max(ans,res); } cout<<ans; return 0; }
SPECIAL PAIRS
求 的个数
子集DP就是枚举ai,然后求F[(1<<n)-1-ai]。
当然FWT的and卷积更简单(模板)
#include<bits/stdc++.h> using namespace std; #define next Next #define gc getchar const int N=5e6+5; int n,a[N],F[N]; /*char buf[1<<21],*p1=buf,*p2=buf; inline int gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}*/ inline int read() { int ret=0,f=0;char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){ret=ret*10+c-48;c=gc();} if(f)return -ret;return ret; } signed main() { n=read(); for(int i=1;i<=n;i++) { a[i]=read(); F[a[i]]++; } int ma=(1<<22)-1; for(int i=0;i<22;i++) for(int j=0;j<=ma;j++) if(j&(1<<i))F[j]+=F[j^(1<<i)]; for(int i=1;i<=n;i++) ans+=F[ma-a[i]]; cout<<ans/2; return 0; }
E. Compatible Numbers
求对于每一个ai,是否存在aj,使
和上一题差不多,就是一个简单的高位前缀和。
#include<bits/stdc++.h> using namespace std; #define next Next #define gc getchar const int N=5e6+5; int n,a[N],F[N]; /*char buf[1<<21],*p1=buf,*p2=buf; inline int gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}*/ inline int read() { int ret=0,f=0;char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){ret=ret*10+c-48;c=gc();} if(f)return -ret;return ret; } signed main() { n=read(); for(int i=1;i<=n;i++) { a[i]=read(); F[a[i]]=a[i]; } int ma=(1<<22)-1; for(int i=0;i<22;i++) for(int j=0;j<=ma;j++) if(j&(1<<i)&&F[j^(1<<i)])F[j]=F[j^(1<<i)]; for(int i=1;i<=n;i++) { if(F[ma-a[i]]>0)printf("%d ",F[ma-a[i]]); else printf("%d ",-1); } return 0; }
E.Vowels
对于24个字母,选出若干字母当“元音”,一个字符串是好的,至少有一个字符是“元音”。
答案是所以情况的好字符串个数的平方的异或和。
最简单的想法,枚举元音的状态,计算有多少个字符串是好的。
考虑如何快速求出F[S],表示元音状态为S,的好字符串个数。发现需要容斥,1个字符-2个字符+......。这样就能用子集DP来解决了
#include<bits/stdc++.h> using namespace std; #define next Next #define gc getchar #define int long long const int N=(1<<24)+5; int n,ans,F[N]; char s[15]; /*char buf[1<<21],*p1=buf,*p2=buf; inline int gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}*/ inline int read() { int ret=0,f=0;char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){ret=ret*10+c-48;c=gc();} if(f)return -ret;return ret; } signed main() { n=read(); for(int i=1;i<=n;i++) { scanf("%s",s+1); int t=0; for(int i=1;i<=3;i++) t|=(1<<(s[i]-'a')); for(int i=t;i>0;i=(i-1)&t) if(__builtin_popcount(i)%2==1)F[i]++; else F[i]--; } for(int i=0;i<24;i++) for(int j=0;j<(1<<24);j++) if(j&(1<<i))F[j]+=F[j^(1<<i)]; for(int i=0;i<(1<<24);i++) ans=ans^(F[i]*F[i]); cout<<ans; }
还有些例题请参考:巨佬博客