回憶一下多項式的卷積$C_k=\sum_{i+j=k}A_i*B_j$數組
咱們能夠用$FFT$來作。學習
甚至在一些特殊狀況下,咱們$C_k=\sum_{ij=k}A_iB_j$也能作(SDOI2015 序列統計)。spa
可是,若是咱們把操做符換一下呢?code
好比這樣?數學
\(C_k=\sum_{i|j=k}A_i*B_j\)io
\(C_k=\sum_{i\&j=k}A_i*B_j\)class
\(C_k=\sum_{i\wedge j=k}A_i*B_j\)基礎
彷佛這就不能用$FFT$來作了。原理
這樣子就有了$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,......)\)
或卷積長成這個樣子:\(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^$次項和後面的$2^$次項
也就是最高位爲$0$與$1$的兩部分。
對於或卷積,咱們有:
\(FWT(A)=\begin{cases}(FWT(A_0),FWT(A_0+A_1)) & n\gt0 \\ A & n=0\end{cases}\)
對於$n=0$的時候,這個是很是顯然的(常數還不顯然了。。。)
啥?你問打個括號,而後中間一個逗號是啥意思?
不是說了這個結果是一個$2^n$維向量?
也就表示前$2^$項是逗號前面的東西,後面那幾項是逗號後面的東西
徹底能夠理解爲將兩個多項式強行先後拼接成一個新的多項式。
好的,咱們來僞證(感性理解)一下$n>0$的時候的式子
對於$A_0$中的任意一項,若是在作$or$卷積的時候,和任意一個$A_1$中的項$or$了一下
那麼它的首位必定是$1$,一定不會對於$FWT(A_0)$產生任何貢獻,
因此$FWT(A)$的前$2^$項必定等於$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$卷積的正確性了
$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$顯然成立
結合律?和前面同樣是知足的。
好的,先把變換的式子寫出來。
\(FWT(A)=\begin{cases}(FWT(A_0+A_1),FWT(A_1)) & n\gt0 \\ A & n=0\end{cases}\)
從某種意義上來講,$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$卷積就證實完啦。
爲了方便打,我就把異或操做用$\oplus$來表示吧(並且這樣彷佛也是一種經常使用的表達方式)
主要緣由是$\wedge$太難打了
表達式:\(C_k=\sum_{i\oplus j=k}A_i*B_j\)
向量式按照上面寫就好了
先寫一下$FWT(A)$吧 \(FWT(A)=\begin{cases}(FWT(A_0)+FWT(A_1),FWT(A_0)-FWT(A_1)) & n>0\\A & n=0\end{cases}\)
\(FWT(A+B)=FWT(A)+FWT(B)\)
這個顯然仍是成立的,理由和上面是同樣的。
接下來仍是證實相同的東西
\(FWT(A)\times FWT(B)=FWT(A\oplus B)\)
證實:
好啦好啦
這樣子$xor$卷積也證實完啦。
因而咱們能夠開心的來寫$FWT$啦
咱們如今能夠在$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})\)
$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; } }