[多项式算法](Part 4)FWT 快速沃尔什变换 学习笔记
其他多项式算法传送门:
[多项式算法](Part 1)FFT 快速傅里叶变换 学习笔记
[多项式算法](Part 2)NTT 快速数论变换 学习笔记
[多项式算法](Part 3)MTT 任意模数FFT/NTT 学习笔记
\(4.Extreme-FWT\)
-
FWT\((Fast\ Walsh-Hadamard\ Transform)\)
中文名称:快速沃尔什变换
(Fast Wrong-Answer Transform)
\(Q:\)有完没完了?\(FWT\)又是什么?现在已经能处理任意情况的多项式乘法了,还要这个干什么?
\(A:\)我也不想学啊,根本背不下来
虽然现在我们可以快速求出\(C=A*B,C_k=\sum_{i+j=k}A_i*B_j\)了,但是现在让你求\(C=A\oplus B,C_k=\sum_{i\oplus j=k}A_i*B_j\),其中\(\oplus\)代表一个位运算符号,如\(|(or),\&(and),\land(xor)\),你该怎么做呢?
这时就到\(FWT\)登场了。
接下来让我们对于\(FWT\)的\(3\)种形式感性理解分别讨论
\(Part1--FWT(or)\)
设\(A_0,A_1\)分别表示长度为\(n=2^x\)的多项式\(A\)的前半部分和后半部分。
首先,给出\(FWT(A)\)的计算方式:
其中\((A,B)\)表示两个多项式相连。
那么\(n=1\)是显然是对的,边界嘛。
至于\(n>1\)的情况如何理解?
对于\(A_0\)和\(A_0\)中两个数下标\(or\)起来一点还在\(A_0\)中(二进制下最高位为\(0\),是前半部分),那么就只对前半部分有贡献。
对于\(A_1\)和\(A_1\)中两个数,同理只对后半部分有贡献。
对于\(A_0\)和\(A_1\)中的两个数,思考\(FWT(A)_k\)的意义,有:
因为当\(i|k=k,j|k=k\)时,有\((i|j)|k=k\),满足\(FWT\)的可合并性质。
那么因为\(A_1\)下标二进制最高位为\(1\),所以\(or\)起来只对后半部分产生贡献。
贡献就是\(A_0\)对\(A_1\)的贡献(\(A_1\)已经贡献过自己了,不用再加)。
那么式子就很明显了。
同时根据\(FWT(A)\)的意义,容易发现\(FWT(A_0+A_1)=FWT(A_0)+FWT(A_1)\)。
接下来证明\(FWT(A|B)=FWT(A)*FWT(B)\)(保证\(or\)卷积答案的正确性,要不然\(FWT\)就没有用了)。
当\(n=1\)时,性质显然成立
当\(n>1\)时,:
由数学归纳法得知,此性质成立。
那么\(or\)的\(FWT\)就很好写了~~代码在后面。
\(Part2--FWT(and)\)
\(and\)的\(FWT\)就和\(or\)的很类似了。
因为\(A_0\)和\(A_1\)最高位不同,那么\(and\)后只对\(A_0\)有贡献。
类似\(or\)的,可以得到\(FWT(A)\)的计算方式:
至于证明就不写了,和\(or\)的类似,写着麻烦。
\(Part3--FWT(xor)\)
最迷的\(xor\)来了
说实话,在网上找了许多\(Blog\),似乎都没有给出构造方法,那么我也不会啊
你就当是某位神仙找的规律吧
首先是\(FWT(A)\)的计算方式:
看着都恶心,这怎么构造出来的啊\(QAQ\)
那么根据定义,很容易证明\(FWT(A\pm B)=FWT(A)\pm FWT(B)\)
因为\(FWT\)是一个线性组合,满足以上性质。
然后是\(FWT(A\land B)=FWT(A)*FWT(B)\)
这个也可以用数学归纳法证明,详见参考资料
\(Q:\)等等……是不是少了什么?\(FWT\)后怎么变回去呢?
\(A:\)这还不简单接下来的过程就是\(IFWT\)了!
\(Part4--IFWT(or)\)
\(Emm...\)至于\(IFWT\)呢就很简单了,把变换倒过来即可。
(这不是废话吗)
那么对于\(or\)的\(IFWT\),考虑之前有\(FWT\)的方程:
也就是:
此时定义\(IFWT(A)_0=FWT(A_0),IFWT(A)_1=FWT(A_1)\) ,也就有:
\(En...\)真简单
\(Part5--IFWT(and)\)
类似\(or\)的\(IFWT\),可以直接得到:
过程类似,这里就不多赘述。。
\(Part6--IFWT(xor)\)
\(xor\)的\(IFWT\)出人意料地一样简单。
由定义得:
解方程就简单了。
最后有:
终于完了
那么接下来就是看图写话看定义写代码过程了:
这里为了节省代码量把\(6\)个函数写一起了(因为框架大致类似)。
代码:
#include <cstdio>
#include <cstring>
typedef long long ll;
const int Mod=998244353,Inv2=(Mod+1)>>1;//Inv2 2在mod998244353下的逆元
int n,a[1<<17],b[1<<17],as[1<<17],bs[1<<17];
void FWT(int *A,int op,int t)
//op [1/-1][FWT/IFWT]
//t [1,2,3][or/and/xor]
{
for(int i=2;i<=n;i<<=1)//i 区间长度
for(int j=0,m=i>>1;j<n;j+=i)//j 区间左端 m 区间大小一半
for(int k=0;k<m;++k)//k 正在算第几个数
if(t==1)A[j+m+k]=((ll)A[j+m+k]+A[j+k]*op+Mod)%Mod;
else if(t==2)A[j+k]=((ll)A[j+k]+A[j+m+k]*op+Mod)%Mod;
else
{
int A0=A[j+k],A1=A[j+m+k];
A[j+k]=(ll)(A0+A1)*(op==1?1:Inv2)%Mod;
A[j+m+k]=(ll)(A0-A1+Mod)*(op==1?1:Inv2)%Mod;
}
}
int main()
{
scanf("%d",&n),n=1<<n;
for(int i=0;i<n;++i)scanf("%d",&as[i]);
for(int i=0;i<n;++i)scanf("%d",&bs[i]);
for(int t=1;t<=3;++t)//分别计算or/and/xor
{
memcpy(a,as,sizeof(int)*n);
memcpy(b,bs,sizeof(int)*n);
FWT(a,1,t),FWT(b,1,t);
for(int i=0;i<n;++i)a[i]=a[i]*1LL*b[i]%Mod;
FWT(a,-1,t);
for(int i=0;i<n;++i)printf("%d%c",a[i],i==n-1?'\n':' ');
}
return 0;
}
代码应该很好懂,就不解释了。
我只能说:\(FWT\)真好背真好写。
参考资料:(\(Dalao\ TQL\))