【学习笔记】fwt&&fmt&&子集卷积
- 前言:yyb神仙的博客
FWT
基本思路:将多项式变成点值表达,点值相乘之后再逆变换回来得到特定形式的卷积;
多项式的次数界都为\(2^n\)的形式,\(A_0\)定义为前一半多项式(下标二进制第一位为\(0\)),\(A_1\)同理定义;
\((A,B)\)表示多项式\(A\)和\(B\)的直接拼接,FWT的结果都是一个点值表达,相乘表示点值相乘;
下面这些变换都满足线性,记\(n\)为二进制位数,复杂度:\(O(n\times 2^n)\);
or卷积
-
形式:
\[(A|B)_{k} = \sum_{i|j=k}A_i\times B_j \] -
定义变换:
\[FWT(A) = (FWT(A_0),FWT(A_0+A_1)) \]则:\(FWT(A|B)=FWT(A) \times FWT(B)\)
-
归纳证明:
-
注意到\(A|B=(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)\) ;
\[\begin{align} &FWT(A|B)\\ &=FWT(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)\\ &=(FWT(A_0|B_0),FWT(A_0|B_0+A_0|B_1+A_1|B_0+A_1|B_1))\\ &=(FWT(A_0)\times FWT(B_0),FWT(A_0)\times FWT(B_0)+FWT(A_0) \\ &\times FWT(B_1)+FWT(A_1)\times FWT(B_0)+FWT(A_1)\times FWT(B_1))\\ &=(FWT(A_0),FWT(A_0+A_1))\times(FWT(B_0),FWT(B_0+B_1))\\ &=FWT(A)\times FWT(B) \end{align} \] -
逆变换:
\[IFWT(A) = (IFWT(A_0),IFWT(A_1-A_0)) \] -
代码:
void fwt(int*A,int F){
for(int i=1;i<n;i<<=1)
for(int j=0;j<n;j+=i<<1)
for(int k=0;k<i;++k){
if(~F)A[j+k+i]=(A[j+k+i]+A[j+k])%mod;
else A[j+k+i]=(A[j+k+i]-A[j+k]+mod)%mod;
}
}
and卷积
-
形式:
\[(A\&B)_{k} = \sum_{i\&j=k}A_i\times B_j \] -
定义变换:
\[FWT(A) = (FWT(A_0+A_1),FWT(A_1)) \]则:\(FWT(A\&B)=FWT(A) \times FWT(B)\)
-
归纳证明:
注意到:\(A\&B=(A_0\&B_0+A_0\&B_1+A_1\&B_0,A_1\&B_1)\)
同理..
-
逆变换:
\[IFWT(A)=(IFWT(A_0-A_1),IFWT(A_1)) \] -
代码
void fwt(int*A,int F){ for(int i=1;i<n;i<<=1) for(int j=0;j<n;j+=i<<1) for(int k=0;k<i;++k){ if(~F)A[j+k]=(A[j+k]+A[j+k+i])%mod; else A[j+k]=(A[j+k]-A[j+k+i]+mod)%mod; } }
xor卷积
-
形式
\[(A \oplus B)_{k} = \sum_{i \oplus j=k}A_i\times B_j \] -
定义变换:
\[FWT(A) = (FWT(A_0+A_1),FWT(A_0-A_1)) \]则:\(FWT(A \oplus B)=FWT(A) \times FWT(B)\)
-
归纳证明:
注意到:\(A \oplus B=(A_0 \oplus B_0+A_1\oplus B_1 ,A_0\oplus B_1+A_1\oplus B_0)\)
同理..
-
逆变换:
\[IFWT(A)=(IFWT(A_0+A_1)/2,IFWT(A_0-A_1)/2) \] -
代码:
void fwt(int*A,int F){ for(int i=1;i<n;i<<=1) for(int j=0;j<n;j+=i<<1) for(int k=0;k<i;++k){ int x=A[j+k],y=A[j+k+i]; A[j+k]=(x+y)%mod,A[j+k+i]=(x-y+mod)%mod; if(!~F)A[j+k]=(ll)A[j+k]*iv2%mod,A[j+k+i]=(ll)A[j+k+i]*iv2%mod; } }
FMT
基本思路:将二进制的每一位看成一维,枚举每一维做前缀和就可以得到集合的前缀和;
直接上代码了......
子集前缀和(= or)
for(int i=0;i<n;++i)
for(int j=1<<i;j<1<<n;++j)
if(j>>i&1)f[j]+=f[j^(1<<i)];
超集前缀和(= and)
for(int i=0;i<n;++i)
for(int j=(1<<n)-1;j>=1<<i;--j)
if(j>>i&1)f[j^(1<<i)]+=f[j];
fmt的逆变换只需要将第二维反过来减即可;
优点是代码短,常数小,复杂度也是\(O(n \times 2^n)\)
子集卷积和
-
\(S,T\)是集合,求\(C:C_S = \sum_{S \subset T} A_T \times B_{S-T}\)
-
设\(A_{i,S}=[|S|=i]\times A_S\)
-
令\(C_i = \sum_{j\le i} A_{j}|B_{i-j}\)
-
这样就把问题变成了or卷积
-
由于我们只关心满足\(|T|=i\)的\(C_{i,T}\),所以是不会多算的;
-
时间复杂度:\(O(n^2 \times 2^n)\)
-
#include<bits/stdc++.h> #define ll long long #define mod 998244353 #define rg register #define il inline using namespace std; const int N=22; int n,m,p,f[N][1<<N],g[N][1<<N],d[N],fa[N],all,ny[3010]; int w[1<<N],cnt[1<<N],iw[1<<N],e[N]; il void inc(int&x,int y){x+=y;if(x>=mod)x-=mod;} il void dec(int&x,int y){x-=y;if(x<0)x+=mod;} il void fwt(int*A,int F){ if(~F){ for(rg int i=0;i<n;++i) for(rg int x=1<<i,j=x;j<all;++j) if(j&x)inc(A[j],A[j^x]); }else{ for(rg int i=0;i<n;++i) for(rg int x=1<<i,j=all-1;~j;--j) if(j&x)dec(A[j],A[j^x]); } } int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} il bool check(int S){ for(rg int i=0;i<n;++i)fa[i]=i,d[i]=0; for(int i=0;i<n;++i)if(S>>i&1) for(int j=i+1;j<n;++j)if(S>>j&1){ if(!(e[i]>>j&1))continue; d[i]++,d[j]++; int fu=find(i),fv=find(j); if(fu!=fv)fa[fu]=fv; } int lst=-1; for(rg int i=0;i<n;++i)if(S>>i&1){ if(!~lst)lst=find(i); if(lst!=find(i) || (d[i]&1))return true; } return false; } il int val(int x){ if(!p)return 1; if(p&1)return x; return (ll)x*x%mod; } int main(){ // freopen("walk.in","r",stdin); // freopen("walk.out","w",stdout); scanf("%d%d%d",&n,&m,&p);all=1<<n; for(rg int i=0;i<m;++i){ int u,v; scanf("%d%d",&u,&v),u--,v--; e[u]|=1<<v;e[v]|=1<<u; } for(rg int i=0;i<n;++i)scanf("%d",&w[1<<i]); ny[1]=1;for(rg int i=2;i<=2100;++i)ny[i]=(ll)(mod-mod/i)*ny[mod%i]%mod; fwt(w,1); for(rg int i=0;i<all;++i){ iw[i]=ny[w[i]]; iw[i]=val(iw[i]); w[i]=val(w[i]); cnt[i]=cnt[i>>1]+(i&1); } for(rg int i=0;i<all;++i)if(check(i))g[cnt[i]][i]=w[i]; for(rg int i=0;i<=n;++i)fwt(g[i],1); f[0][0]=1;fwt(f[0],1); for(rg int i=1;i<=n;++i){ int *F=f[i];//不加寻址优化是会T的 for(rg int j=1;j<=i;++j){ int *a=f[i-j],*b=g[j]; for(rg int k=0;k<all;++k){ inc(F[k],(ll)a[k]*b[k]%mod); } } fwt(F,-1); for(rg int j=0;j<all;++j)F[j]=cnt[j]==i?(ll)F[j]*iw[j]%mod:0; if(i!=n)fwt(f[i],1); } cout<<f[n][all-1]<<endl; return 0; }