[学习笔记]FWT——快速沃尔什变换
解决涉及子集配凑的卷积问题
一、介绍
1.基本用法
FWT快速沃尔什变换学习笔记
就是解决一类问题:
$f[k]=\sum_{i\oplus j=k}a[i]*b[j]$
基本思想和FFT类似。
首先转化成为另一个多项式$FWT(A),FWT(B)$
使得:$FWT(A\oplus B)=FWT(A)×FWT(B)$
这里,$×$是按位乘。这个是$O(n)$的。
然后,再$IFWT$回去即可。
类似于,直接过马路不好走。先从左边走上一座天桥,再从天桥走过去,再到马路右侧走下天桥。
就变成了$O(nlogn)$
$FWT$虽然不是非常容易理解,但是比较容易记忆。
(虽然一定要理解)
类比$FFT$的写法,就可以比较轻松记忆。
就是分治压缩、合并、分治解压的过程。
#pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize("Ofast") #include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } il void prin(int x){ if(x/10) prin(x/10); putchar(x%10+'0'); } namespace Miracle{ const int N=131072+4; const int mod=998244353; const int inv2=499122177; int a[N],b[N]; int c[N],d[N]; int e[N]; int n; void _or(int *f,int op){ for(reg p=2;p<=n;p<<=1){ int len=p/2; for(reg k=0;k<n;k+=p){ for(reg l=k;l<k+len;++l){ if(op==1)f[l+len]=(f[l+len]+f[l])%mod; else f[l+len]=(f[l+len]-f[l]+mod)%mod; } } } } void _and(int *f,int op){ for(reg p=2;p<=n;p<<=1){ int len=p/2; for(reg k=0;k<n;k+=p){ for(reg l=k;l<k+len;++l){ if(op==1)f[l]=(f[l+len]+f[l])%mod; else f[l]=(f[l]-f[l+len]+mod)%mod; } } } } void _xor(int *f,int op){ for(reg p=2;p<=n;p<<=1){ int len=p/2; for(reg k=0;k<n;k+=p){ for(reg l=k;l<k+len;++l){ int x=f[l],y=f[l+len]; if(op==1){ f[l]=(x+y)%mod; f[l+len]=(x-y+mod)%mod; } else{ f[l]=(ll)(x+y)*inv2%mod; f[l+len]=(ll)(x-y+mod)%mod*inv2%mod; } } } } } int main(){ scanf("%d",&n); n=(1<<n); for(reg i=0;i<n;++i) rd(a[i]),c[i]=a[i]; for(reg i=0;i<n;++i) rd(b[i]),d[i]=b[i]; _or(c,1);_or(d,1); for(reg i=0;i<n;++i) e[i]=(ll)c[i]*d[i]%mod; _or(e,-1); for(reg i=0;i<n;++i){ prin(e[i]);putchar(' ');c[i]=a[i],d[i]=b[i]; }putchar('\n'); _and(c,1);_and(d,1); for(reg i=0;i<n;++i) e[i]=(ll)c[i]*d[i]%mod; _and(e,-1); for(reg i=0;i<n;++i){ prin(e[i]);putchar(' ');c[i]=a[i],d[i]=b[i]; }putchar('\n'); _xor(c,1);_xor(d,1); for(reg i=0;i<n;++i) e[i]=(ll)c[i]*d[i]%mod; _xor(e,-1); for(reg i=0;i<n;++i){ prin(e[i]);putchar(' ');//c[i]=a[i],d[i]=b[i]; }putchar('\n'); return 0; } } int main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/22 15:08:15 */
2.子集卷积
https://www.cnblogs.com/Dance-Of-Faith/p/8818211.html
多加一维i,强制记录涉及集合sz大小
外层枚举sz
O(2^n*n^2)
(推荐使用FWT,因为比FMT常数小)
二、例题
留坑
[FWT] UOJ #310. 【UNR #2】黎明前的巧克力
三、FFT、NTT、FWT的比较
留坑
没啥可比较的。处理思路一致。
就是运算符的问题吧。
四、FWT、FMT的比较
留坑
FMT好写,FWT的与或卷积的第一步可以取代FMT
upda:2019.4.17
FMT可以代替FWT的与或卷积。IFMT把+改成-即可
(xor暂时不知道具体含义,估计也可以代替?)
实际上
FMT很辣鸡
相比之下,FWT做的事情完全包含FMT,并且常数是FMT的1/2!
[WC2018]州区划分(这个题我人傻常数大,必须用FWT卡常才能过)
所以还是写FWT吧