[學習筆記]快速沃爾什變換 (FWT)

FWT的簡介

一般FWT\text{FWT}用來解決一下問題:

  • Ck=ij=kAiBjC_k=\sum_{i|j=k}A_iB_j
  • Ck=i&j=kAiBjC_k=\sum_{i\&j=k}A_iB_j
  • Ck=ij=kAiBjC_k=\sum_{i\oplus j=k}A_iB_j

實現的大概思路就是就是先把他們轉化成fwt(A)\text{fwt(A)}(類似FFT\text{FFT}的點值表達),然後對應爲相乘,最後在還原爲多項式(整個過程很類似與快速傅里葉變換)

or 卷積

現在要做到這個的快速卷積:Ck=ij=kAiBjC_k=\sum_{i|j=k}A_iB_j

定義ABA|B爲多項式的or\text{or}卷積,顯然 AB=BAA|B=B|A(交換律),(A+B)C=AC+BC(A+B)|C=A|C+B|C(結合律)

定義fwt(A)[k]=ikAifwt(A)[k]=\sum_{i|k}A_i,因爲我們要讓fwt(C)=fwt(A)×fwt(B)fwt(C)=fwt(A)\times fwt(B),這個基於的原理就是若ik=ki|k=kjk=kj|k=k,就能推出(ij)k=k(i|j)|k=k,那麼原來是kk子集的或之後還是kk子集。

現在我們來研究如何計算fwt(A)fwt(A),類似FFT\text{FFT}地用分治的方法,設A0A_0爲當前位爲00的項,A1A_1就是剩下部分:
fwt(A)={(fwt(A0),fwt(A0+A1))n>0An=0fwt(A)=\begin{cases}(fwt(A_0),fwt(A_0+A_1))& n>0\\A& n=0\end{cases}根據fwt(A)fwt(A)的定義可以知道:fwt(A+B)=fwt(A)+fwt(B)fwt(A+B)=fwt(A)+fwt(B),根據定義也可以知道如果最高位爲11,那麼就把fwt(A0+A1)fwt(A_0+A_1)算作AA的後半部分就可以了,爲00的話子集就是00

現在我們來證明一下fwt(AB)=fwt(A)×fwt(B)fwt(A|B)=fwt(A)\times fwt(B)
fwt(AB)=fwt((AB)0,(AB)1)=fwt(A0B0,A0B1+A1B0+A1B1)=(fwt(A0B0),fwt(A0B0+A1B0+A0B1+A1B1))=(fwt(A0)×fwt(B0),fwt(A0+A1)×fwt(B0+B1))=(fwt(A0),fwt(A0+A1))×(fwt(B0),fwt(B0,B1))=fwt(A)×fwt(B)fwt(A|B)=fwt((A|B)_0,(A|B)_1)\\=fwt(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1)\\=(fwt(A_0|B_0),fwt(A_0|B_0+A_1|B_0+A_0|B_1+A_1B_1))\\=(fwt(A_0)\times fwt(B_0),fwt(A_0+A_1)\times fwt(B_0+B_1))\\=(fwt(A_0),fwt(A_0+A_1))\times (fwt(B_0),fwt(B_0,B_1))\\=fwt(A)\times fwt(B)這裏用到了數學歸納法,首先n=0n=0的情況肯定成立,然後我們假設較小的規模成立,以此推導更大的規模(這裏是12\frac{1}{2}

最後返回來的dfwtdfwt就是根據上面的fwtfwt設計的,變換如下:
dfwt(A)={dfwt(A0),dfwt(A1A0)n>1An=0dfwt(A)=\begin{cases}dfwt(A_0),dfwt(A_1-A_0)&n>1\\A&n=0\end{cases}

and 卷積

這個和上面的卷積極其類似,直接給出結論:
fwt(A)={(fwt(A0+A1),fwt(A0))n>0An=0fwt(A)=\begin{cases}(fwt(A_0+A_1),fwt(A_0))& n>0\\A& n=0\end{cases}逆變換如下:
dfwt(A)={dfwt(A0A1),dfwt(A1)n>0An=0dfwt(A)=\begin{cases}dfwt(A_0-A_1),dfwt(A_1)&n>0\\A&n=0\end{cases}

xor 卷積

這就是重頭戲了,我們要解決:Ck=ij=kAiBjC_k=\sum_{i\oplus j=k}A_iB_j

先定義fwt(A)[x]=2d(xi)Ai2[d(xi)1]Aifwt(A)[x]=\sum_{2|d(x\cap i)}A_i-\sum_{2|[d(x\cap i)-1]}A_idd是二進制11的個數,這樣定義是基於一個結論:
d(x(ij))=d(xi)+d(xj)2d(xij)d(x\cap (i\oplus j))=d(x\cap i)+d(x\cap j)-2d(x\cap i\cap j)考慮每一位的合法性,就可以推知所有情況,這個自己枚舉一下可能的組合就行了,然後:
fwt(A)[x]fwt(B)[x]=2d(xi)2d(xj)AiBj2[d(xi)1]2d(xj)AiBj......fwt(A)[x]\oplus fwt(B)[x]=\sum_{2|d(x\cap i)}\sum_{2|d(x\cap j)}A_iB_j-\sum_{2|[d(x\cap i)-1]}\sum_{2|d(x\cap j)}A_iB_j......你可以發現如果d(xi)d(x\cap i)d(xj)d(x\cap j)奇偶性相同的話那麼前面的符號是正,否則前面的符號是負,我們觀察上面的結論,發現d(x(ij))d(x\cap (i\oplus j))d(xi)+d(xj)d(x\cap i)+d(x\cap j)的奇偶性相同,恰好就對應了。

現在來考慮正變換,基本思路還是考慮最高位,左半邊的話都選自己和選自己再選對半邊都可以,而且不需要變號,因爲不會產生新的11位,兩個都選右半邊的話就需要變號,那麼就這樣算:
fwt(A)={(fwt(A0+A1),fwt(A0A1))n>0An=0fwt(A)=\begin{cases}(fwt(A_0+A_1),fwt(A_0-A_1))&n>0\\A&n=0\end{cases}然後逆變化就是:ifwt(A)={(ifwt(A0+A1)2,ifwt(A0A1)2)n>0An=0ifwt(A)=\begin{cases}(\frac{ifwt(A_0+A_1)}{2},\frac{ifwt(A_0-A_1)}{2})&n>0\\A&n=0\end{cases}
然後貼一個板題的代碼:

#include <cstdio>
const int M = 200005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,a[M],b[M],A[M],B[M],inv2=(MOD+1)/2;
void fwt_or(int *a,int op)
{
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;k++)
			{
				if(op==1) a[i+j+k]=(a[i+j+k]+a[j+k])%MOD;
				else a[i+j+k]=(a[i+j+k]-a[j+k]+MOD)%MOD;
			}
}
void fwt_and(int *a,int op)
{
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;k++)
			{
				if(op==1) a[j+k]=(a[j+k]+a[i+j+k])%MOD;
				else a[j+k]=(a[j+k]-a[i+j+k]+MOD)%MOD;
			}
}
void fwt_xor(int *a,int op)
{
	for(int i=1;i<n;i<<=1)
		for(int p=i<<1,j=0;j<n;j+=p)
			for(int k=0;k<i;k++)
			{
				int x=a[j+k],y=a[i+j+k];
				a[j+k]=(x+y)%MOD;
				a[i+j+k]=(x+MOD-y)%MOD;
				if(op==-1)
				{
					a[j+k]=1ll*a[j+k]*inv2%MOD;
					a[i+j+k]=1ll*a[i+j+k]*inv2%MOD;
				}
			}
}
void init()
{
	for(int i=0;i<n;i++)
		A[i]=a[i],B[i]=b[i];
}
signed main()
{
	n=1<<read();
	for(int i=0;i<n;i++) a[i]=read();
	for(int i=0;i<n;i++) b[i]=read();
	init();
	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]);
	puts("");
	init();
	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]);
	puts("");
	init();
	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]);
	puts("");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章