FWT (快速沃爾什變換)詳解 以及 K進制FWT


FWT (快速沃爾什變換)詳解 以及 K進制FWT

約定:\(F'=FWT(F)\)

卷積的問題,事實上就是要構造\(F'G'=(FG)'\)

我們常見的卷積,是二進制位上的or ,and ,xor

但正式來說,是集合冪指數 上的 並 , 交 , 對稱差

為了說人話,這里就不帶入集合冪指數的概念了

一個常識:\(\sum_{T\sube S}(-1)^{|T|}=[S=\empty]\)


or 和 and 卷積

ps: 雖然這兩個並不是\(\text{FWT}\),應該叫\(\text{FMT}\)(快速莫比烏斯變換),但是由於常用的是這3個,所以放到一起

這兩種卷積的本質是相同的,所以只解釋\(or\)卷積

or卷積的本質就是高位前綴和

即:\(F'_S=\sum _{T\sube S}F_T\)

正確性:

\(\forall S,F'_S \cdot G'_S=(F\cup G)'_S\)

左邊=

\(F'_S \cdot G'_S=\sum _{T\sube S}\sum _{R\sube S}F_T\cdot G_R\)

右邊=

\((F\cup G)'_S=\sum_{T\sube S}(F \cup G)_S\)

\(=\sum_{T\sube S}\sum_{A,B,A\cup B=S}F_A\cdot G_B\)

\(=\sum_{T \sube S}\sum_{R \sube S}F_T \cdot G_R\)

\[\ \]

卷積實現

其實第一次層循環的意思是枚舉子集中和自己不同的位最高是\(i\)

\(0\)\(1\)轉移即可

void FWT(int n,ll *a){
    for(int i=1;i<n;i<<=1) 
        rep(j,i,n-1) if(j&i) s[j]+=s[j^i];
}
void FWT(int n,ll *a){
    for(int i=1;i<n;i<<1)
        for(int l=0;l<n;l+=i*2)
            for(int j=0;j<l+i;++j) 
                s[j+i]+=s[j];
}

Tips:如果要卡常,可以寫成類似\(\text{FFT}\)的形式,因為優化了訪問順序會快一些

\[\ \]

實現逆卷積

把上面的加換成減,這是一個類似容斥的東西

但是因為是反解,所以這個過程我么通常稱為子集反演

那么每次\(0\)\(1\)的轉移意味着多了一個不同的位置

\(F'_S=\sum_{T\sube S}F_T\)

實際逆卷積就是\(F_S=\sum_{T\sube S}(-1)^{|T\oplus S|} F'_S\)

證明如下:

\(\Leftrightarrow F_S=\sum_{T\sube S}(-1)^{|T\oplus S|} \sum _{R\in T}F_R\)

\(\Leftrightarrow F_S=\sum_{T\sube S}F_R\sum _{T\sube R,R\sube S}(-1)^{|S\oplus R|}\)

\(\Leftrightarrow F_S=\sum_{T\sube S}F_R\sum _{R\sube (S\oplus T)}(-1)^{|R|}\)

帶入上面所提到的\(\sum_{T\sube S}(-1)^{|T|}=[S=\empty]\),成立

void FWT(int n,ll *a,int f){
    for(int i=1;i<n;i<<=1) 
        rep(j,i,n-1) if(j&i) s[j]+=f*s[j^i];
}
void FWT(int n,ll *a,int f){
    for(int i=1;i<n;i<<1)
        for(int l=0;l<n;l+=i*2)
            for(int j=0;j<l+i;++j) 
                s[j+i]+=f*s[j];
}

\[\ \]

\[\ \]

應用 : 子集卷積(可以看luogu)

問題描述: 給定\(F_S,G_T\),求出\(H_{R}=\sum_{S\cup T=R,S\cap T=\empty}F_S\cdot G_T\),設有\(2^n\)個元素

我們知道直接枚舉的復雜度為\(O(3^n)\)

直接應用or卷積無法保證\(S\cap T=\empty\),但是可以再記錄一個占位數量,即把\(F,G\)按照每一位包含1的數量分開成\(n+1\)部分,卷積完成之后

應該滿足1的個數恰好為兩者之和,否則清空

需要\(n\)次卷積,\(n^2\)次轉移,因此復雜度為\(O(n^22^n)\),在漸進意義上更優於\(O(3^n)\)


Xor 卷積

這里要用到一個小性質

\(|A\cap B|+|A\cap C|\equiv |A\cap (B\bigoplus C)| \pmod 2\)

思路介紹:

我們是要構造一個\(F_S\rightarrow G_T\)的變換,使得該變換滿足Xor的性質,且能在較優的時間復雜度內完成,並且能夠在較優的時間內完成反演

由於上面的這條式子,考慮可以構造\(F'_S=\sum_{T}(-1)^{|S\cap T|}F_T\),這樣\((-1)^k\)的系數在\(\mod 2\)意義下可以抵消

正確性

\(\forall S,F'_S \cdot G'_S=(F\bigoplus G)'_S\)

\(F'_S\cdot G'_S=\sum_{T} \sum_{R}(-1)^{|S\cap T|+|S\cap R|}F_T\cdot G_R\)

\(=\sum _T\sum _R(-1)^{|(T\bigoplus R)\cap S|}F_T\cdot G_R\)

顯然這個式子與右邊相同

\[\ \]

卷積實現

考慮和前面相同的方法,枚舉二進制位上最高的\(1\)

之前由於轉移是單向的,所以只需要一次加法,這里由於有了系數同時還是雙向的轉移,所以要格外注意

轉移系數也是比較明顯的

\(0\rightarrow 0 = 1\)

\(0\rightarrow 1 = 1\)

\(1\rightarrow 0 = 1\)

\(1\rightarrow 1 = -1\)

void FWT(int n,ll *a){
    for(int i=1;i<n;i<<=1) {
        rep(j,0,n-1) if(~j&i) {
            ll t=a[j+i];
            a[j+i]=a[j]-t;
            a[j]=a[j]+t;
        }
    }   
}
void FWT(int n,ll *a){
    for(int i=1;i<n;i<<=1){
        for(int l=0;l<n;l+=i*2) {
            for(int j=l;j<l+i;++j){
                ll t=a[j+i];
                a[j+i]=a[j]-t;
                a[j]+=t;
            }
        }
    }
}

實現逆卷積

考慮再卷一次

\(F''_S=\sum_T\sum_R(-1)^{|S\cap R|+|T\cap R|}F_T\)

\(=\sum_T \sum_R (-1)^{|(S\bigoplus T)\cap R|}F_T\)

\(\because \sum_T (-1)^{|S\cap T|}=\sum_{T\sube S}(-1)^{|T|}2^{|U|-|S|}=[S=\empty]2^{|U|-|S|}\)(其中\(U\)是全集)

\(\therefore F''_S=\sum_S2^{|U|}F_S\)

所以逆卷積就是再卷一遍,最后除去\(n\)即可

void FWT(int n,ll *a,int f){
    for(int i=1;i<n;i<<=1) {
        rep(j,0,n-1) if(~j&i) {
            ll t=a[j+i];
            a[j+i]=a[j]-t;
            a[j]=a[j]+t;
        }
    }   
    if(f==-1) rep(i,0,n-1) a[i]/=n;
}
void FWT(int n,ll *a,int f){
    for(int i=1;i<n;i<<=1){
        for(int l=0;l<n;l+=i*2) {
            for(int j=l;j<l+i;++j){
                ll t=a[j+i];
                a[j+i]=a[j]-t;
                a[j]+=t;
            }
        }
    }
    if(f==-1) for(int i=0;i<n;++i) a[i]/=n;
}

和上面一樣的,可以寫成類似\(\text{FFT}\)的形式卡常

\[\ \]

\[\ \]

拓展 K - FWT

實際上學習了這個拓展能讓你更好地理解\(\text{FWT}\)

不妨考慮\(n\)個維度的情況,每個維度是一個\(0,1,\cdots k-1\)中的數

由於\(k\)進制下不好用集合描述,因此考慮用一個向量\(\vec{V}=\lbrace V_0,V_1,\cdots,V_{n-1}\rbrace,V_i\in[0,k-1]\)表示

一個多項式可以具象地用\(0,1,\cdots,k^n-1\)這個\(k^n\)個位置上的系數表示

\(\text{and,or}\)卷積在\(k\)進制下可以拓展為按位取\(\min,\max\),這個直接累前綴和就可以了,不作贅述

\(k\)進制下的\(\text{xor}\)可以擴展為兩個向量列的取模加法

\(\vec{A}+\vec{B}=\vec{C},C_i=(A_i+B_i)\bmod k\)

也可以描述為不進位的\(k\)進制數加法

其實用\(\text{K-FWT}\)稱呼這個似乎不是很形象,更好的可以稱之為\(\text{n-DFT}\)

也就是說\(\text{K-FWT}\)實際上就是在\(n\)個維度上分別做大小為\(k\)的循環卷積,使用一種結合\(\text{FWT-DFT}\)的方法(因此需要用到\(k\)次單位根\(\omega_k\))

卷積構造

原多項式\(F\)向卷積多項式\(F'\)的轉換系數為\([x^A]F\rightarrow [x^B]F':\omega_k^{A\cdot B}\)

其中\(A\cdot B\)為向量內積,即\(\sum A_i\cdot B_i\)

從中也可以很好地看到\(\text{xor}\)卷積的影子

實現方法上,可以依次枚舉\(0,1,\cdots,n-1\)每一位,將除了這一位上都相同的數取出來

按照這一位上的值做一次\(\text{DFT}\)

需要\(n\)位枚舉,每次枚舉需要做\(k^{n-1}\)\(k^2\)\(\text{DFT}\),因而復雜度為\(O(nk^{n+1})\)

對於\(k\)比較大的情況,如果\(k=2^t\)可以直接用\(\text{FFT/NTT}\),否則還可以參考這個

可以優化到\(O(nk^n\log k)\)

逆卷積

當然是換成\(\text{IDFT}\),最后全部除掉\(k^n\)

正確性上,如果你對於\(\text{IDFT}\)的原理(單位根反演) 有所了解,就能發現

只有所有位置上都相同的情況才會轉移出\(k^n\)的系數

\[\ \]

int w[20]; // 單位根的冪次
void K_FWT(int *F,int n,int f){ // 這個n實際上是上面敘述中的n^k
    static int t[20];
    for(int i=1;i<n;i*=k){
        for(int l=0;l<n;l+=i*k){
            for(int j=l;j<l+i;++j){
                for(int a=0;a<k;++a) 
                    for(int b=t[a]=0;b<k;++b) 
                        t[a]=(t[a]+1ll*F[j+b*i]%P*w[b*(k+f*a)%k])%P;
                for(reg int a=0;a<k;++a) F[j+a*i]=t[a];
            }
        }
    }
	if(f==-1) {
        ll base=qpow(n);
        rep(i,0,n-1) F[i]=F[i]*base%P;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM