FWT快速沃爾什變換學習筆記
1、FWT用來干啥啊
回憶一下多項式的卷積\(C_k=\sum_{i+j=k}A_i*B_j\)
我們可以用\(FFT\)來做。
甚至在一些特殊情況下,我們\(C_k=\sum_{i*j=k}A_i*B_j\)也能做(SDOI2015 序列統計)。
但是,如果我們把操作符換一下呢?
比如這樣?
\(C_k=\sum_{i|j=k}A_i*B_j\)
\(C_k=\sum_{i\&j=k}A_i*B_j\)
\(C_k=\sum_{i\wedge j=k}A_i*B_j\)
似乎這就不能用\(FFT\)來做了。
這樣子就有了\(FWT\)——用來解決多項式的位運算卷積
2、FWT大概要怎么搞啊
我們想一想\(FFT\)在干啥?
先對於一個多項式求出他在若干個單位根的點值表示法
再將多項式乘起來,最后再復原。
那么,我們可不可以用一個類似的思路呢?
先將多項式求出另外一個多項式\(FWT(A)\),再將對應的位置乘起來,最后再復原?
也就是\(FWT(C)=FWT(A)*FWT(B)\)(這個不是卷積,是對應位置相乘)?
廢話,顯然可以,要不然我還寫什么\(FWT\)呢?
我們先來一點奇奇怪怪的記號吧。
因為多項式可以看成一個\(n\)維向量
所以,我們定義以下東西:
\(A+B=(A_0+B_0,A_1+B_1,......)\)
\(A-B=(A_0-B_0,A_1-B_1,......)\)
\(A\oplus B=(\sum_{i\oplus j=0}A_i*B_j,\sum_{i\oplus j=1}A_i*B_j,......)\)
3、或(or)卷積
或卷積長成這個樣子:\(C_k=\sum_{i|j=k}A_i*B_j\)
寫成向量的形式也就是這樣子:\(A|B=(\sum_{i|j=0}A_i*B_j,\sum_{i|j=1}A_i*B_j,......)\)
很顯然的一點:這個東西滿足交換律,也就是\(A|B=B|A\)
再來仔細的看看,這個東西也滿足結合律。
簡單的證明一下\((A+B)|C=(\sum_{i|j=0}(A_i+B_i)*C_j,......)\)
很明顯可以把括號拆開,然后分成兩個\(\sum\),也就是\(A|C+B|C\)
我們這樣子定義一下:
對於一個多項式\(A\)(最高次項是\(2^n\)),
我們把它分成兩部分\(A_0,A_1\),分別表示前\(2^{n-1}\)次項和后面的\(2^{n-1}\)次項
也就是最高位為\(0\)與\(1\)的兩部分。
對於或卷積,我們有:
對於\(n=0\)的時候,這個是非常顯然的(常數還不顯然了。。。)
啥?你問打個括號,然后中間一個逗號是啥意思?
不是說了這個結果是一個\(2^n\)維向量?
也就表示前\(2^{n-1}\)項是逗號前面的東西,后面那幾項是逗號后面的東西
完全可以理解為將兩個多項式強行前后拼接成一個新的多項式。
好的,我們來偽證(感性理解)一下\(n>0\)的時候的式子
對於\(A_0\)中的任意一項,如果在做\(or\)卷積的時候,和任意一個\(A_1\)中的項\(or\)了一下
那么它的首位一定是\(1\),必定不會對於\(FWT(A_0)\)產生任何貢獻,
所以\(FWT(A)\)的前\(2^{n-1}\)項一定等於\(FWT(A_0)\)。
后面這個東西看起來就很不好證明了,所以我們先考慮證明點別的東西。
\(FWT(A+B)=FWT(A)+FWT(B)\)
證明(偽):
對於一個多項式\(A\)的\(FWT(A)\),它一定只是若干個原多項式中的若干項的若干倍的和。
即\(FWT(A)\)一定不包含原多項式的某幾項的乘積。
如果包含了原多項式的乘積,那么在求出卷積之后,
我們發現此時的結果與某個多項式自身的某兩項的乘積有關
但是我們在或卷積的結果式中發現一定只與某個多項式的某一項與另一個多項式的某一項有關。
因此,我們知道\(FWT(A)\)的任意一項只與\(A\)的某幾項的和有關
因此,我們這個式子可以這樣拆開。
此時,這個偽證成立。(這話怎么這么別扭)
但是怎么說,總是感覺證明后面都要貼上一個偽字,
如果我們能夠知道\(FWT(A)\)是個啥東西,我們就可以把這個字給扔掉了
先給出結論:
對於\(or\)卷積而言,\(FWT(A)[i]=\sum_{j|i=i}A[j]\)
它基於的原理呢?
如果\(i|k=k,j|k=k\),那么就有\((i|j)|k=k\)
這樣說很不清楚,我們現在來證明后半部分為啥是\(FWT(A_0+A_1)\)
因為\(A_0\)中取一項和\(A_1\)中取一項做\(or\)卷積,顯然貢獻會產生到\(A_1\)中去
首先,對於\(A_1\)中的任意兩項的貢獻,一定在\(A_1\)中,即使去掉了最高位,此時也會產生這部分的貢獻
但是現在在合並\(A_0\)和\(A_1\)的貢獻的時候,還需要考慮\(A_0\)的貢獻
相當於只丟掉了最高位,因此,在\(A_0\)與\(A_1\)對應項的\(FWT\)的和就是我們現在合並之后的結果
所以也就是\(FWT(A_0+A_1)=FWT(A_0)+FWT(A_1)\)
這樣子,我們來考慮或卷積,也就是\(FWT(A|B)\)
我們要證明它等於\(FWT(A)\times FWT(B)\),這樣子我們就可以放心的使用\(or\)卷積了
證明:
這是一個數學歸納法的證明,請仔細看一看QwQ
當只有一項的時候這個是顯然的。
好啦,這樣就證明出了\(or\)卷積的正確性了
4、和(and)卷積
\(and\)卷積是這樣的:\(C_k=\sum_{i\&j=k}A_i*B_j\)
寫成向量的形式:\(A\&B=(\sum_{i\&j=0}A_i*B_j,\sum_{i\&j=1}A_i*B_j,......)\)
交換律?\(A\&B=B\&A\)顯然成立
結合律?和前面一樣是滿足的。
好的,先把變換的式子寫出來。
從某種意義上來說,\(and\)和\(or\)和很類似的。
我們這樣看:
\(0|0=0,0|1=1,1|0=1,1|1=1\)
\(0\&0=0,0\&1=0,1\&0=0,1\&1=1\)
都是\(3\)個\(0/1\),然后剩下的那個只有一個
既然如此,其實我們也可以用\(or\)卷積類似的東西很容易的證明\(and\)卷積
\(FWT(A+B)=FWT(A)+FWT(B)\)
大致的偽證就是\(FWT(A)\)是關於\(A\)中元素的一個線性組合,顯然滿足分配率
接着只要再證明
\(FWT(A)\times FWT(B)=FWT(A\&B)\)就行了
方法仍然是數學歸納法。
證明:
好啦,這樣子\(and\)卷積就證明完啦。
5、異或(xor)卷積
為了方便打,我就把異或操作用\(\oplus\)來表示吧(而且這樣似乎也是一種常用的表達方式)
主要原因是\(\wedge\)太難打了
表達式:\(C_k=\sum_{i\oplus j=k}A_i*B_j\)
向量式按照上面寫就行了
先寫一下\(FWT(A)\)吧
\(FWT(A+B)=FWT(A)+FWT(B)\)
這個顯然還是成立的,理由和上面是一樣的。
接下來還是證明相同的東西
\(FWT(A)\times FWT(B)=FWT(A\oplus B)\)
證明:
好啦好啦
這樣子\(xor\)卷積也證明完啦。
於是我們可以開心的來寫\(FWT\)啦
6、IFWT
我們現在可以在\(O(nlogn)\)的時間復雜度里面得到\(FWT(A\oplus B)\),其中\(\oplus\)表示一個位運算。
得到了\(FWT\)之后我們需要還原這個數組,也就是\(IFWT\)(叫\(UFWT\)也沒啥問題??)
怎么求?
正向的過程我們知道了,逆向的反着做啊。
所以:
\(or\)卷積:$$IFWT(A)=(IFWT(A_0),IFWT(A_1)-IFWT(A_0))$$
\(and\)卷積:$$IFWT(A)=(IFWT(A_0)-IFWT(A_1),IFWT(A_1))$$
\(xor\)卷積:$$IFWT(A)=(\frac{IFWT(A_0)+IFWT(A_1)}{2},\frac{IFWT(A_0)-IFWT(A_1)}{2})$$
7、代碼實現
\(or\)卷積的代碼
void FWT(ll *P,int opt)
{
for(int i=2;i<=N;i<<=1)
for(int p=i>>1,j=0;j<N;j+=i)
for(int k=j;k<j+p;++k)
P[k+p]+=P[k]*opt;
}
\(and\)卷積只需要在\(or\)卷積的基礎上修改一點點就好了
void FWT(ll *P,int opt)
{
for(int i=2;i<=N;i<<=1)
for(int p=i>>1,j=0;j<N;j+=i)
for(int k=j;k<j+p;++k)
P[k]+=P[k+p]*opt;
}
\(xor\)卷積其實也差不多(這個是在模意義下的\(FWT\))
如果不是在模意義下的話,開一個\(long\ long\),然后把逆元變成直接除二就好了。
void FWT(int *P,int opt)
{
for(int i=2;i<=N;i<<=1)
for(int p=i>>1,j=0;j<N;j+=i)
for(int k=j;k<j+p;++k)
{
int x=P[k],y=P[k+p];
P[k]=(x+y)%MOD;P[k+p]=(x-y+MOD)%MOD;
if(opt==-1)P[k]=1ll*P[k]*inv2%MOD,P[k+p]=1ll*P[k+p]*inv2%MOD;
}
}
Upd:
寫了個好看點的板子,這樣就和\(FFT\)長得很像了。
void FWT_or(int *a,int opt)
{
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(opt==1)a[i+j+k]=(a[j+k]+a[i+j+k])%MOD;
else a[i+j+k]=(a[i+j+k]+MOD-a[j+k])%MOD;
}
void FWT_and(int *a,int opt)
{
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(opt==1)a[j+k]=(a[j+k]+a[i+j+k])%MOD;
else a[j+k]=(a[j+k]+MOD-a[i+j+k])%MOD;
}
void FWT_xor(int *a,int opt)
{
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(opt==-1)a[j+k]=1ll*a[j+k]*inv2%MOD,a[i+j+k]=1ll*a[i+j+k]*inv2%MOD;
}
}
