快速沃爾什變換(FWT)及K進制異或卷積&快速子集變換(FST)講解


前言:

  $FWT$是用來處理位運算(異或、與、或)卷積的一種變換。位運算卷積是什么?形如$f[i]=\sum\limits_{j\oplus k==i}^{ }g[j]*h[k]$的卷積形式(其中$\oplus$為位運算)就是位運算卷積。如果暴力枚舉的話,時間復雜度是$O(n^2)$,但運用$FWT$來解決就可達到$O(nlog_{n})$的時間復雜度。$FST$則是借助$FWT$來進行的對子集卷積的優化,相當於$FWT$的一個應用。

FWT

與卷積

對於與運算,有一個結論:$(i\&j)\&k==k<-->(i\&k==k)\&\&(j\&k==k)$。

那么我們就可以構造一個求父集和的函數$F(i)=\sum\limits_{j\&i==i}^{ }f(i)$,由此我們可以推出:

$G(k)*H(k)$

$=\sum\limits_{i\&k==k}^{ }g(i)\sum\limits_{j\&k==k}^{ }h(j)$

$=\sum\limits_{(i\&j)\&k==k}^{ }g(i)*h(j)$

$=\sum\limits_{t\&k==k}^{ }\sum\limits_{i\&j==t}^{ }g(i)*h(j)$

因為$f(t)=\sum\limits_{i\&j==t}^{ }g(i)*h(j)$,

所有上式$=\sum\limits_{t\&k==k}^{ }f(t)=F(k)$

 因此我們只需要將$g,h$正變換成$G,H$,然后對應位相乘得到$F$后再將$F$逆變換回去即可得到$f$。

那么如何正變換?

以下面為例:

00 a

01 b

10 c

11 d

我們從最低位開始考慮,每次只考慮只有當前位不同的兩個數之間的影響。顯然求父集只有$1$會對$0$有貢獻,因此我們將$01$的值加到$00$上,將$11$的值加到$10$上,再看下一位,同樣將$11$的值加到$01$上,將$10$的值加到$00$上。這樣最后$00$的值為$a+b+c+d$,$01$的值為$b+d$,$10$的值為$c+d$,$11$的值為$d$。同樣逆變換就是將$1$的值從$0$上減掉即可。

附上代碼

void fwt_and(int *a,int opt)
{
    for(int k=2;k<=n;k<<=1)
    {
        for(int i=0,t=k>>1;i<n;i+=k)
        {
            for(int j=i;j<i+t;j++)
            {
                if(opt==1)
                {
                    a[j]=(a[j]+a[j+t])%mod;
                }
                else
                {
                    a[j]=(a[j]-a[j+t]+mod)%mod;
                }
            }
        }
    }
}

或卷積

或卷積和與卷積類似,對於或卷積同樣有結論:$(i|j)|k==k<-->(i|k==k)\&\&(j|k==k)$

這次我們需要構造一個求子集和的函數$G(i)=\sum\limits_{j|i==i}^{ }g(j)$,推導過程和與卷積類似。

對於正變換,顯然只有$0$對$1$有貢獻;對於逆變換,只需要將$0$的值從$1$中減掉即可。

附上代碼

void fwt_or(int *a,int opt)
{
    for(int k=2;k<=n;k<<=1)
    {
        for(int i=0,t=k>>1;i<n;i+=k)
        {
            for(int j=i;j<i+t;j++)
            {
                if(opt==1)
                {
                    a[j+t]=(a[j+t]+a[j])%mod;
                }
                else
                {
                    a[j+t]=(a[j+t]-a[j]+mod)%mod;
                }
            }
        }
    }
}

異或卷積

對於異或卷積,我們設$bit(i)$代表$i$的二進制中$1$的奇偶性,因此有一個結論(這里異或用$\oplus$表示):$bit(i\&k)\oplus bit(j\&k)=bit((i\oplus j)\&k)$

對於原多項式$g$構造$G(i)=\sum\limits_{j=0}^{2^n-1}(-1)^{bit(j\&i)}g(j)$

開始推導:

$G(k)*H(k)$

$=\sum (-1)^{bit(i\&k)}g(i)\sum (-1)^{bit(j\&k)}h(j)$

$=\sum (-1)^{bit((i\oplus j)\& k)}g(i)*h(j)$

$=\sum (-1)^{bit(t\&k)}\sum\limits_{i\oplus j==t}^{ }g(i)*h(j)$

$=\sum (-1)^{bit(t\&k)}f(t)$

$=F(k)$

對於正變換,我們同樣從最低位向最高位考慮,每次只考慮只有當前位不同的兩個數之間的影響。對於每對$0$和$1$(設值分別為$a$和$b$),$0\&0=0$和$0\&1=0$都不會影響$bit$的值,所以$0$那個位置的值變成$a+b$;$1\&0=0$不會影響$bit$的值,但$1\&1=1$會影響$bit$的值(相當於在前面乘上一個$-1$的系數),因此$1$那個位置的值變成$a-b$。對於逆變換,相當於我們現在知道兩個位置$x=a+b$,$y=a-b$,求$a$和$b$,可以得到$a=\frac{x+y}{2},b=\frac{x-y}{2}$。

附上代碼

void fwt_xor(int *a,int opt)
{
    int tmp;
    for(int k=2;k<=n;k<<=1)
    {
        for(int i=0,t=k>>1;i<n;i+=k)
        {
            for(int j=i;j<i+t;j++)
            {
                tmp=a[j];
                a[j]=(a[j]+a[j+t])%mod;
                a[j+t]=(tmp-a[j+t]+mod)%mod;
                if(opt==-1)
                {
                    a[j]=1ll*a[j]*inv%mod;
                    a[j+t]=1ll*a[j+t]*inv%mod;
                }
            }
        }
    }
}

K進制異或卷積

可以發現二進制的異或運算相當於不進位加法即每一位對應相加后對$2$取模,而與運算相當於不進位乘法即每一位對應相乘后對$2$取模,$bit$相當於求二進制每一位的和對$2$取模。那么我們將這些在二進制下的運算擴展到$K$進制可以發現同樣滿足上述的結論,但對於從$g$求$G$的部分每個數前面的系數的底數是$-1$,顯然這個系數不能擴展到$K$進制。那么我們現在就需要找一個系數$w$滿足$w^0,w^1,w^2……w^{k-1}$都各不相同且$w^i=w^{i\%k}$。從$FFT$中我們知道了復數單位根這個東西,那么我們完全可以將$w$取$K$次單位根,這樣就可以滿足以上性質了!類比二進制亦或的正變換也可以得出$K$進制的正變換。

 FST

$FST$通常用來優化一類子集$DP$,例如$f(S)=\sum g(T)*h(S-T)$,其中$T$是$S$的子集。

對於這個方程我們不能直接用$FWT$卷積,因為如果$g(i)*h(j)$會對$f(k)$有貢獻就要求$i|j=k,i\&j=0$。

顯然位運算卷積不能同時滿足這兩個要求,那么我們將方程變成二維表示$f[S][i]=\sum\limits_{j=1}^{i}\sum\limits_{a|b==S}^{ }g[a][j]*h[b][i-j]$

其中的第二維表示集合大小,對於$f[S][i]$只有當$i=|S|$時才有值,這樣第一維保證並集為$S$,第二維保證交集為空集,所以其他不合法的狀態不會影響答案。

這樣我們就能對式子進行卷積:$F[S][i]=\sum\limits_{j=1}^{i}G[S][j]*H[S][i-j]$。求出每個$F$的值再$FWT$逆變換回去即可。

原題中的$f[S]$就是二維狀態中的$f[S][|S|]$。

時間復雜度為$O(2^n*n^2)$。


免責聲明!

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



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