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

快速沃尔什变换

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\{\begin{matrix} FWT(A_{0}),FWT(A_{0}+A_{1}) &(n\neq 1)\\ A & (n= 1) \end{matrix}\right. \]

\[IFWT(A)= \left\{\begin{matrix} IFWT(A_{0}),IFWT(A_{1}-A_{0}) &(n\neq 1)\\ A & (n= 1) \end{matrix}\right. \]

(2)与运算

\[FWT(A)= \left\{\begin{matrix} FWT(A_{0}+A_{1}),FWT(A_{1}) &(n\neq 1)\\ A & (n= 1) \end{matrix}\right. \]

\[IFWT(A)= \left\{\begin{matrix} IFWT(A_{0}-A_{1}),IFWT(A_{1}) &(n\neq 1)\\ A & (n= 1) \end{matrix}\right. \]

(3)或运算

\[FWT(A)= \left\{\begin{matrix} FWT(A_{0}+A_{1}),FWT(A_{0}-A_{1}) &(n\neq 1)\\ A & (n= 1) \end{matrix}\right. \]

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

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;
}

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

posted @ 2020-07-22 16:51  萧瑟秋风今又是又是今  阅读(283)  评论(0编辑  收藏  举报