• 浅谈快速沃尔什变换


    快速沃尔什变换(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;
    }
    
  • 相关阅读:
    vue组件通信类型限制
    vue父子组件通信
    vue组件data必须是函数
    vue组件模块抽离
    vue局部组件语法糖
    leetcode刷题-47全排列2
    leetcode刷题-46全排列
    leetcode刷题-43字符串相乘
    leetcode刷题-40组合总和2
    leetcode刷题-39组合总和
  • 原文地址:https://www.cnblogs.com/hbyer/p/10308815.html
Copyright © 2020-2023  润新知