zoukankan      html  css  js  c++  java
  • 浅谈快速沃尔什变换

    快速沃尔什变换(fwt)

    (fwt)是一种快速计算位运算卷积的算法,一般包括按位或卷积,按位与卷积和异或卷积。

    按位或(or)卷积

    对于多项式(A,B,C),定义(oplus)为卷积符号,即(Aoplus B = C)

    那么,按位或卷积就是:

    [C_k=sum_{i~or~j=k}A_icdot B_j ]

    类比于(FFT),现在,我们的任务就是找到一种变换,记这种变换为(fwt(A)),则要满足(fwt(A) imes fwt(B)=fwt(C)),其中( imes)表示每一位相乘,且(Aoplus B=C)

    经过前人的大力研究,可以发现:

    [fwt(A)_i=sum_{j~or~i=i}A_j ]

    是满足性质的,证明很简单,直接带进去可得:

    [egin{align} fwt(C)_k&=sum_{j~or~k=k}sum_{a~or~b=j}A_acdot B_b\ &=sum_{a~or~k=k}A_acdot sum_{b~or~k=k}B_b\ &=fwt(A)_kcdot fwt(B)_k end{align} ]

    即得证。

    那么,考虑怎样快速的进行(fwt)变换。

    然后有一个这样的式子:

    [fwt(A)= egin{cases} (fwt(A_1),fwt(A_1)+fwt(A_2))&n>0\ A_0&n=0 end{cases} ]

    其中,((A,B))表示把两个多项式的系数拼起来,感性理解一下就好了。

    (A_1)表示多项式前半段,(A_2)表示后半段。

    (n=0)的时候显然,我们只需要关心上面那个是为什么就好了。

    对于前半段的第(i)项,(i)的最高位肯定是(0),那么后半段显然对他没有影响,前半段的影响就是(fwt(A_1)_i)

    对于后半段的第(i)项,(i)的最高位是(1),所以最高位取(0)时是(fwt(A_1)_i),取(1)时是(fwt(A_2)_i),所以一共就是(fwt(A_1)+fwt(A_2))

    然后这玩意形式其实和(FFT)差不太多,复杂度也是(O(nlog n))

    代码:

    void fwt_or(int *r) {
    	for(int i=1;i<n;i<<=1)
    		for(int j=0;j<n;j+=(i<<1))
    			for(int k=0;k<i;k++)
                    r[i+j+k]=(r[i+j+k]+r[j+k])%mod;
    }
    

    按位与(and)卷积

    和上面差不多的,定义:

    [fwt(A)_i=sum_{j&i=i}A_i ]

    证明也差不多,这里不赘述了。

    那么,算的话就是:

    [fwt(A)= egin{cases} (fwt(A_1)+fwt(A_2),fwt(A_2))&n>0\ A_0&n=0 end{cases} ]

    只要考虑按位与的性质,高位为(1)时只能选高位为(1)的,否则都能选。

    代码:

    void fwt_and(int *r) {
    	for(int i=1;i<n;i<<=1)
    		for(int j=0;j<n;j+=(i<<1))
    			for(int k=0;k<i;k++)
                    r[j+k]=(r[i+j+k]+r[j+k])%mod;
    }
    

    异或(xor)卷积

    这里的定义就不是很相同了。

    定义:

    [fwt(A)_i=sum_{j=0}^{n}(-1)^{cnt(i&j)}A_j ]

    其中,(i&j)表示按位与,(cnt(x))表示(x)二进制下(1)的个数。(这到底是怎么想到的。。)

    带进去交换下枚举顺序可得:

    [egin{align} fwt(C)_i&=sum_{j=0}^{n}(-1)^{cnt(i&j)}C_j\ &=sum_{j=0}^{n}(-1)^{cnt(i&j)}sum_{aoplus b=j}A_aB_b\ &=sum_{a=0}^{n}A_asum_{b=0}^{n}B_b(-1)^{cnt(i&(aoplus b))} end{align} ]

    我们考虑下指数上的那一块东西:(cnt(i&(aoplus b))),分情况讨论下这个与(cnt(i&a)+cnt(i&b))的关系:(由于多位和一位没有区别,这里只讨论一位)

    (i)(0),显然这一位不计入答案,不管。

    (a,b)都为(1)的话,(aoplus b=0),不计入答案,但是注意到这里是((-1))的指数,其实((-1)^0=(-1)^2),不妨看做是(2),那么这两个相等。

    (a,b)有一个为(1),前后显然相等,都为(1)

    (a,b)都为(0),显然也相等,都为(0)

    所以式子可以改写成这样:

    [egin{align} fwt(C)_i&=sum_{a=0}^{n}A_asum_{b=0}^{n}B_b(-1)^{cnt(i&a)+cnt(i&b)}\ &=sum_{a=0}^{n}(-1)^{cnt(i&a)}A_asum_{b=0}^{n}(-1)^{cnt(i&b)}B_b\ &=fwt(A)_icdot fwt(B)_i end{align} ]

    所以,证毕。

    那么,快速做这个的式子:

    [fwt(A)= egin{cases} (fwt(A_1)+fwt(A_2),fwt(A_1)-fwt(A_2))&n>0\ A_0&n=0 end{cases} ]

    具体的,考虑前一半的时候,最高位为(0),直接加起来就好了。

    对于后一半,最高位为(1),如果选的数最高位也为(1)(cnt)就多了(1),也就是整体多乘了个(-1),所以就是(fwt(A_1)-fwt(A_2))

    代码:

    void fwt_xor(int *r) {
    	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=r[j+k],y=r[i+j+k];
                    r[j+k]=(x+y)%mod,r[i+j+k]=(x-y)%mod;
    			}
    }
    

    逆沃尔什变换

    知道了上面的,这玩意其实就很简单了。

    对于按位或,就是知道了(fwt(A_1))(fwt(A_1)+fwt(A_2)),求出两个分别是多少,直接减一下就完了:

    [ifwt(A)=(ifwt(A_1),ifwt(A_2)-ifwt(A_1)) ]

    对于按位与,也差不多:

    [ifwt(A)=(ifwt(A_1)-ifwt(A_2),ifwt(A_2)) ]

    对于异或,是知道(fwt(A_1)+fwt(A_2))(fwt(A_1)-fwt(A_2)),那么加起来除以(2)就是第一个,减一下除以(2)就是第二个,即:

    [ifwt(A)=(frac{ifwt(A_1)+ifwt(A_2)}{2},frac{ifwt(A_1)-ifwt(A_2)}{2}) ]

    模板

    给一个模板大全吧,题目来自luogu4717

    #include<bits/stdc++.h>
    using namespace std;
     
    void read(int &x) {
        x=0;int f=1;char ch=getchar();
        for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
        for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
    }
     
    void print(int x) {
        if(x<0) putchar('-'),x=-x;
        if(!x) return ;print(x/10),putchar(x%10+48);
    }
    void write(int x) {if(!x) putchar('0');else print(x);putchar('
    ');}
    
    const int maxn = 2e5+10;
    const int mod = 998244353;
    const int inv2 = 499122177;
    
    int bit,n,a[maxn],b[maxn],c[maxn],ina[maxn],inb[maxn];
    
    void fwt_or(int *r,int op) {
    	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(op==1) r[i+j+k]=(r[i+j+k]+r[j+k])%mod;
    				else r[i+j+k]=(r[i+j+k]-r[j+k])%mod;
    }
    
    void fwt_and(int *r,int op) {
    	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(op==1) r[j+k]=(r[i+j+k]+r[j+k])%mod;
    				else r[j+k]=(r[j+k]-r[i+j+k])%mod;
    }
    
    void fwt_xor(int *r,int op) {
    	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=r[j+k],y=r[i+j+k];
    				if(op==1) r[j+k]=(x+y)%mod,r[i+j+k]=(x-y)%mod;
    				else r[j+k]=1ll*(x+y)*inv2%mod,r[i+j+k]=1ll*(x-y)*inv2%mod;
    			}
    }
    
    int main() {
    	read(bit);n=1<<bit;
    	for(int i=0;i<n;i++) read(ina[i]);
    	for(int i=0;i<n;i++) read(inb[i]);
    	// or
    	memcpy(a,ina,sizeof ina);memcpy(b,inb,sizeof inb);
    	fwt_or(a,1),fwt_or(b,1);for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod;
    	fwt_or(a,-1);for(int i=0;i<n;i++) printf("%d ",(a[i]+mod)%mod);puts("");
    	// and 
    	memcpy(a,ina,sizeof ina);memcpy(b,inb,sizeof inb);
    	fwt_and(a,1),fwt_and(b,1);for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod;
    	fwt_and(a,-1);for(int i=0;i<n;i++) printf("%d ",(a[i]+mod)%mod);puts("");
    	// xor
    	memcpy(a,ina,sizeof ina);memcpy(b,inb,sizeof inb);
    	fwt_xor(a,1),fwt_xor(b,1);for(int i=0;i<n;i++) a[i]=1ll*a[i]*b[i]%mod;
    	fwt_xor(a,-1);for(int i=0;i<n;i++) printf("%d ",(a[i]+mod)%mod);puts("");
    	return 0;
    }
    
  • 相关阅读:
    Java实现 蓝桥杯 算法提高 特等奖学金(暴力)
    Java实现 蓝桥杯 算法提高 特等奖学金(暴力)
    Java实现 蓝桥杯 算法提高 GPA(暴力)
    Java实现 蓝桥杯 算法提高 GPA(暴力)
    Java实现 蓝桥杯 算法提高 GPA(暴力)
    Java实现 蓝桥杯 算法提高 套正方形(暴力)
    Java实现 蓝桥杯 算法提高 套正方形(暴力)
    第一届云原生应用大赛火热报名中! helm install “一键安装”应用触手可及!
    云原生时代,2个方案轻松加速百万级镜像
    Knative 基本功能深入剖析:Knative Serving 自动扩缩容 Autoscaler
  • 原文地址:https://www.cnblogs.com/hbyer/p/10308815.html
Copyright © 2011-2022 走看看