• 【学习笔记】fwt&&fmt&&子集卷积


    FWT

    基本思路:将多项式变成点值表达,点值相乘之后再逆变换回来得到特定形式的卷积;

    多项式的次数界都为(2^n)的形式,(A_0)定义为前一半多项式(下标二进制第一位为(0)),(A_1)同理定义;

    ((A,B))表示多项式(A)(B)的直接拼接,FWT的结果都是一个点值表达,相乘表示点值相乘;

    下面这些变换都满足线性,记(n)为二进制位数,复杂度:(O(n imes 2^n))

    or卷积

    • 形式:

      [(A|B)_{k} = sum_{i|j=k}A_i imes B_j ]

    • 定义变换:

      [FWT(A) = (FWT(A_0),FWT(A_0+A_1)) ]

      则:(FWT(A|B)=FWT(A) imes FWT(B))

    • 归纳证明:

    • 注意到(A|B=(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)) ;

      [egin{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) imes FWT(B_0),FWT(A_0) imes FWT(B_0)+FWT(A_0) \ & imes FWT(B_1)+FWT(A_1) imes FWT(B_0)+FWT(A_1) imes FWT(B_1))\ &=(FWT(A_0),FWT(A_0+A_1)) imes(FWT(B_0),FWT(B_0+B_1))\ &=FWT(A) imes 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 imes B_j ]

    • 定义变换:

      [FWT(A) = (FWT(A_0+A_1),FWT(A_1)) ]

      则:(FWT(A&B)=FWT(A) imes 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 imes B_j ]

    • 定义变换:

      [FWT(A) = (FWT(A_0+A_1),FWT(A_0-A_1)) ]

      则:(FWT(A oplus B)=FWT(A) imes FWT(B))

    • 归纳证明:

      注意到:(A oplus B=(A_0 oplus B_0+A_1oplus B_1 ,A_0oplus B_1+A_1oplus 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 imes 2^n))

    子集卷积和

    • (S,T)是集合,求(C:C_S = sum_{S subset T} A_T imes B_{S-T})  

    • (A_{i,S}=[|S|=i] imes A_S)

    • (C_i = sum_{jle i} A_{j}|B_{i-j})

    • 这样就把问题变成了or卷积

    • 由于我们只关心满足(|T|=i)(C_{i,T}),所以是不会多算的;

    • 时间复杂度:(O(n^2 imes 2^n))

    • 【WC2018】州区划分

      #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;
      }
      
  • 相关阅读:
    CopyOnWriteArrayList设计思路与源码分析
    点击页面按钮以excel保存到本地
    上传图片
    关于重复点击的
    去首尾空格还有换行问题//把数字换位大写字母//向后台传输数据
    判断输入的时间与当前的时间(判断时间是今天还是以前的)
    前端的一些小技巧
    git 操作大全
    移动web开发常见问题解决方案
    响应式布局
  • 原文地址:https://www.cnblogs.com/Paul-Guderian/p/10826888.html
Copyright © 2020-2023  润新知