• [快速沃尔什变换]-简要笔记


    快速沃尔什变换

    1.适用问题类型

    定义元素含义:

    > 对于有序集合a[n],b[n],记录其中每个元素出现次数,得到频率数组A[n],B[n]。
    >
    > A[i],B[i]分别表示数字i分别在 集合a 与 集合b 出现的次数。 
    >
    > ⊕是一种位运算,分别可以是 or,xor,and运算,运算结果不会使二进制位数增大。
    

    问题:

    step1:从集合a与集合b中各选一个元素x与y,使得x⊕y=i。

    step2:问结果为i的数字会有多少种不同的选法,有多少种i可能会出现,出现的i中最大值为多少。

    解答:

    对所有满足i==j⊕k有序对<j,k>,集合a中j出现次数与集合b中k出现次数之积为当前有序对的结果贡献。

    因此我们只对每个i求出需要求出segma{A[j]*B[k] | j⊕k==i},即为i最终出现的次数,记次数为C[i]。

    得到公式:

    [C_{i}=sum_{j oplus k=i} A_{j} B_{k} ]

    这个公式如果换成 i=j+k ,那么是不是很容易联想到FFT解决卷积问题(时域的卷积等于频域的乘积)。

    既然有解决i=j+k的变换,那也有解决位运算的变化,性质类似,也叫做卷积,变换后也可以卷积变为乘积。

    上面由异或推导出的公式可以称作异或卷积,而针对几种卷积形式的变换,叫做沃尔什变换。

    2.定理

    公式推导略不会,以下结论参考了快速沃尔什变换如果有兴趣可以看

    下为沃尔什变换在不同运算符下的递归定义。

    (1)或运算

    [FWT(A)= left{egin{matrix} FWT(A_{0}),FWT(A_{0}+A_{1}) &(n eq 1)\ A & (n= 1) end{matrix} ight. ]

    [IFWT(A)= left{egin{matrix} IFWT(A_{0}),IFWT(A_{1}-A_{0}) &(n eq 1)\ A & (n= 1) end{matrix} ight. ]

    (2)与运算

    [FWT(A)= left{egin{matrix} FWT(A_{0}+A_{1}),FWT(A_{1}) &(n eq 1)\ A & (n= 1) end{matrix} ight. ]

    [IFWT(A)= left{egin{matrix} IFWT(A_{0}-A_{1}),IFWT(A_{1}) &(n eq 1)\ A & (n= 1) end{matrix} ight. ]

    (3)或运算

    [FWT(A)= left{egin{matrix} FWT(A_{0}+A_{1}),FWT(A_{0}-A_{1}) &(n eq 1)\ A & (n= 1) end{matrix} ight. ]

    [IFWT(A)= left{egin{matrix} frac{IFWT(A_{0})+IFWT(A_{1})}{2}, frac{IFWT(A_{0})-IFWT(A_{1})}{2} &(n eq 1)\ A & (n= 1) end{matrix} ight. ]

    3.代码

    于是乎,到了喜闻乐见的模板时间

    细节写在注释中了

    /************************
    以下stu都表示正变换或逆变换.
    stu为1时为正变换,为0是为逆
    不同的卷积对应不同的最内层代码就ok变换
    注意取模,对于异或卷积的除2操作需要逆元
    注意a[i]和b[i]都为频次
    *************************/
    void get() {
    //卷积转换为乘积当然要乘一下啦
    //注意A和B都要从原数组经历一次FWT
    //结果存在A中,B如果需要可以重复利用
    	for (int i = 0; i < n; i++) a[i] *= b[i];
    }
    void FWT(int f[], bool stu = 1) {
        for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1){
            for (int i = 0; i < n; i += o){
                for (int j = 0; j < k; j++){
                    {//或卷积
                        if(!stu)f[i+j+k] += f[i+j] ;
                        else f[i+j+k] -= f[i+j] ;
                    }
                    {//与卷积
                        if(!stu) f[i+j] += f[i+j+k] ;
                        else f[i+j] -= f[i+j+k] ;
                    }
                    {//异或卷积
                        int u=f[i+j],v=f[i+j+k];
                        f[i+j]=u+v;
                        f[i-j]=u-v;
                        if(!stu){
                            f[i+j]>>=1;
                            f[i+j+k]>>=1;
                    }
                }
            }
        }
    }
    

    4.例题

    牛客2020暑期多校训练营第二场

    题意:从序列A中取出k个数字,求取出数字的最大异或和。输出k从1到n的所有结果。

    做法:把原数组转换成频次数组a,然后对原始频次数组不断进行卷积,卷了几次就是对应几次的数值出现频次。可以先复制一份到数组b,对b进行fwt,然后对a进行fwt,将两者fwt结果相乘,在对a进行ifwt变回频次数组,的到选两个的结果,再对a进行fwt,再相乘,在ifwt,类推得到19次结果。因为单个数字范围总共为1<<18,所以进行19次就可以,后面不会有更大的结果了。

    代码

    #include<iostream>
    #include<cstdio>
    using namespace std;
    typedef long long ll;
    const int maxn = 2e5+50;
    int n;
    int a[1<<18],b[1<<18];
    int ans[maxn];
    //stu==1 fwt   stu==0 ifwt
    
    int MaxNum;
    
    //O(wlogw) stu=1 fwt   stu=2 ifwt
    void fwt(int *arr,bool stu){//分治求fwt
    	for(int q=2;q<=MaxNum;q<<=1){
    		int k=q>>1;
    		for(int i=0;i<MaxNum;i+=q){
    			for(int j=0;j<k;j++){
    				int u=arr[i+j],v=arr[i+j+k];
    				//看情况取模
    				arr[i+j]=u+v;
    				arr[i+j+k]=u-v;
    				if(!stu){
    					arr[i+j]>>=1;//
    					arr[i+j+k]>>=1;
    				}
    			}
    		}
    	}
    }
    int main(){
    	int n;
    	MaxNum=1<<18;
    	scanf("%d",&n);
    	int tp;
    	for(int i=1;i<=n;i++){
    		scanf("%d",&tp);
    		a[tp]=b[tp]=1;
    		ans[1]=max(ans[1],tp);
    	}
    	fwt(b,true);//fwt(b),不用变回来
    	for(int i=2;i<=20;i++){
    		fwt(a,true);//fwt(a)
    		for(int j=0;j<MaxNum;j++){//原函数卷积等于fwt后乘积
    			a[j]*=b[j];
    		}
    		fwt(a,false);//ifwt() 倒回原函数
    		for(int j=0;j<MaxNum;j++){
    			if(a[j]){//只关注是否出现,所以变为1.
    				a[j]=1;ans[i]=max(ans[i],j);
    			}
    		}
    	}
    	for(int i=20;i<=n;i++){
    		ans[i]=ans[i-2];
    	}
    	for(int i=1;i<=n;i++){
    		printf("%d ",ans[i]);
    	}
    	return 0;
    }
    
    

    如果文章有错误或存在疑问,欢迎在评论区留言。

  • 相关阅读:
    iOS 笔记-AFN使用中的遇到的问题
    iOS 笔记-incompatible pointer types initializing 'NSMutableString *' with an expression of type 'NSString *'警告处理
    iOS 笔记-SRT视频字幕的解析与同步
    iOS-笔记 字符编码
    iOS-笔记 设置导航栏的样式
    iOS 笔记-自定义的导航栏按钮
    iOS 笔记-删除插件的方法
    iOS 笔记-关于用户交互的那些事
    iOS BUG整理--[__NSCFNumber length]: unrecognized selector sent to instance 崩溃解决
    iOS BUG整理-Data argument not used by format string的警告处理
  • 原文地址:https://www.cnblogs.com/greenpepper/p/13361769.html
Copyright © 2020-2023  润新知