FWT 學習筆記
想盡量講得本質一點。
首先有一個引出問題叫做 集合冪級數
其中,\(opt\) 是集合的並交補運算,而 \(i,j,k\) 也都是集合的意思
當我們把 \(i,j,k\) 看成二進制表示,那么集合中的每一個元素的選/不選對應二進制的 \(1/0\) , \(opt\) 變成了 \(or,and,xor\) 的一種
所以問題變成了這樣:給定兩個長度為 \(2^n\) 的序列 \(a_i,b_i\) (不夠長用 \(0\) 補全),求出一個序列 \(c\) ,滿足 \(\forall i,c_i=\sum_{j \ opt\ k=i}a_jb_k\)
這個時候我們可以使用,FWT ,\(\text{Fast Walsh-Hadamard Transform}\) ,快速沃爾什變換。
FFT
發現 \(fwt\) 和 \(fft\) 的英文很像,所以我們考慮,類似的思路
關於 \(fft\) ,我們有一個經典思路是:(圖源:pyb 的 ppt)
其中,我們能做到 \(O(n\log n)\) 的 \(DFT\) 的原因在於我們有兩個重要操作叫做 奇偶分段 蝴蝶變換 ,也就是說我們把原來的東西進行分治的操作后可以 \(O(1)\) 的合並回去,\(O(n\log n)\) 的 \(IDFT\) 在於一個 \(w\) 的性質的應用,我們不妨嘗試把這樣的思想套進 FWT
記住上圖!
FWT
現在我們有這樣的目標:找到一種變換 \(FWT\) ,使得
其中,\(\times\) 表示對應位相乘,然后通過 \(FWT(C)\) 復原 \(C\)
顯然,我們的 FWT 應該是對於原序列的一種線性組合,即某一項只是若干個原多項式中的若干項的若干倍的和
因為我們本來的 \(c_i\) 就是數個 \(a_jb_k\) 的和,而 FWT 是對應位相乘,如果其出現原多項式某幾項的乘積,那么顯然 GG
所以顯然有的是 \(FWT(A+B)=FWT(A)+FWT(B)\)
這里不妨約定一些記號:
$ 𝐴 = 𝑎_𝑖 ,𝐵 = 𝑏_𝑖 , 𝐶 = 𝑐_𝑖 $
$ 𝐴 + 𝐵 = 𝑎_0 + 𝑏_0, 𝑎_1 + 𝑏_1,\dots $
$ 𝐴 − 𝐵 = 𝑎_0 − 𝑏_0, 𝑎_1 − 𝑏_1,\dots$
$ 𝐴\oplus𝐵 = \sum_{𝑖⨁𝑗=0} 𝑎_𝑖 ∗ 𝑏_𝑗 , \sum_{𝑖⨁𝑗=1} 𝑎_𝑖 ∗ 𝑏_𝑗 ,\dots$
默認所有的數列都有 \(2^n\) 項,即可以理解為集合元素個數有 \(𝑛\) 個
$ 𝐴_0$ 代表數列 \(𝐴\) 的前 \(2^{𝑛−1}\) 項,即最高位(第一個)不選的所有集合,\(𝐴_1\) 代表數列 \(𝐴\) 中的后 \(2 ^{𝑛−1}\) ,即最高位(第一個)選的所有集合
FWTor
假設現在我們的 \(opt\) 就是 \(or\) ,下面簡記 \(A|B=\sum_{j|k=i} a_jb_k\)
下面講得可能有點小亂
FWT
總體想法是仿照 FFT ,考慮一個遞歸的形式構造出 FWT
顯然,當 \(n=0\) 的時候,\(FWT(A)=A\)
下面討論 \(n>0\) 的情況,我們首先考慮奇偶分段,假裝我們已經知道了 \(FWT(A_0),FWT(A_1)\)
於是,一個粗略的 FWT 出現了: \(FWT(A)=Merge(FWT(A_0),FWT(A_1))\)
\(Merge\) 表示直接拼接,xjb 舉一個例子:\(Merge(998,244)=998244\)
我們知道 \(FWT(C)\) 需要等於 \(FWT(A)\times FWT(B)\)
還知道 \(C_0=A_0B_0\) ,$ C_1=A_0B_1+A_1B_0+A_1 B_1$
發現上面那個顯然不靠譜,我們對應位相乘后,只會得到 \(FWT(C_0)=FWT(A_0)FWT(B_0),FWT(C_1)=FWT(A_1)FWT(B_1)\) 這樣一個錯誤。
也就是說后半部分還需要有 \(FWT(A_0)\) 的信息甩進去,所以:(這里不妨將 FWT 當成一個神秘的抽象函數)
(回顧 \(+\) 的記號:$ 𝐴 + 𝐵 = 𝑎_0 + 𝑏_0, 𝑎_1 + 𝑏_1,\dots $)
可是這樣還是有問題呀,這樣對應位相乘,好像會多一個 \(FWT(A_0)FWT(B_0)\) 項的貢獻出現在 \(FWT(C_1)\) 中
但值得注意的是, \(FWT(C)\) 並不一定需要就是 \(C\) 了,我們不妨將這個問題交給 \(IFWT\) 處理,
至少我們這里保證了信息是完全的,並且符合我們心目中奇偶分段和蝴蝶變換的要求
也就是說像這樣給出來后,IFWT 有操作空間,並且 FWT 時間的確是 \(n\log n\)
當然,我們還需要保證 \(FWT(A)\times FWT(B)=FWT(C)\)
這里采用類似數學歸納法的方式證明,思路來自 pyb 的 ppt ,
即假設已知 \(FWT(A_0)\times FWT(B_0)=FWT(C_0),FWT(A_0)FWT(B_1)+FWT(A_1)FWT(B_0),FWT(A_1)FWT(B_1)=FWT(C_1)\) ,那么
小心 \(Merge\) 的括號打的位置
第一個等號:按 FWT 定義展開
第二個等號:按 \(\times\) 定義對應位相乘
第三個等號:利用歸納法得到的結論
第四個等號:把 \(A_0B_0\) 理解成 \(A^{\prime}_0\) , \(A_0B_1+A_1B_0+A_1B_1\) 理解成 \(A^{\prime}_1\)
第五個等號:前文在定義 Merge 的后面一點點所述:
還知道 \(C_0=A_0B_0\) ,$ C_1=A_0B_1+A_1B_0+A_1 B_1$
所以我們歸納證明了 \(FWT(A)\times FWT(B)=FWT(C)\)
IFWT
(小劇場)
FWT: 至高無上的 IFWT !吾交給汝一個使命:干掉 $FWT(A) \times FWT(B) $ 之后出現在 \(FWT(C_1)\) 中的 \(FWT(A_0B_0)\) 項!主不需要它!
IFWT:(心里mmp:彩筆 FWT)哈哈哈!看我容斥!
(突然發現自己好智障)
直接給出來:
其中傳入的 \(A\) 是經歷了 \(FWT\) 的產物,小證一手:
\(FWT(A)\times FWT(B)=FWT(C_0)+FWT(C_0+C_1)\)
所以:
\(IFWT(FWT(C_0)+FWT(C_0+C_1))=Merge(IFWT(FWT(C_0)),IFWT(C_0+C_1-C_0))=Merge(IFWT(FWT(C_0)),IFWT(FWT(C_1)))=C\)
FWT 的本質
我們考慮 FWT 的代碼怎么寫,這里再嫖一張 pyb 的 ppt

發現 \(FWT\) 做的事情就是不斷地讓箭頭的起點加到箭頭的終點,按照紅綠藍的順序一層一層地加,可以有代碼:
void FWT(ll *A){
for(int i=2;i<=N;i<<=1) //i 線段長度
for(int p=i>>1,j=0;j<N;j+=i)//j 哪一個部分
for(int k=j;k<j+p;++k) //k 嫖取信息
A[k+p]+=A[k];
}
我們仔細觀察這些箭頭究竟都干了什么:
對於 \(111\) 而言,它嫖走了 \(110,101,011\) 的信息
對於 \(110\) 而言,它嫖走了 \(101,010\) 的信息
對於 \(101\) 而言,它嫖走了 \(100,001\) 的信息
對於 \(100\) 而言,它飄走了 \(000\) 的信息
你發現了嗎?也就是說,每個位置獲取的是它二進制少掉一個 \(1\) 后的位置的信息
而在獲取這些少掉了 \(1\) 的位置的信息之前,這些少掉了 \(1\) 的位置也獲取了它們所需要的信息,所以,我們剛剛不是說 \(FWT\) 可以看成一個“抽象函數”嗎,它的“解析式”其實長這樣:
\(FWT(A)_i\) 表示 \(FWT(A)\) 的第 \(i\) 項,這里之所以用 \(\subseteq\) 是因為 FWT 本身其實就是在解決“集合冪級數”。
可以帶入 \(FWT(A)\times FWT(B)\) 驗證一下看是不是就是 \(FWT(C)\)
所以,我們也可以從另外一個角度去證明 FWT 的時間復雜度,即是 \(\sum_{0}^{1<<n}popcount(i)=n2^{n-1}\)
上面給出 \(FWT\) 的代碼,其實很容易寫出 \(IFWT\) 的代碼:
void IFWT(ll *A){
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)
A[k+p]-=A[k];//唯一不同點
}
所以,我們容易把它整合起來:
void FWT(ll *A,int op){
for(int i=2;i<=N;i<<=1) //i 線段長度
for(int p=i>>1,j=0;j<N;j+=i)//j 哪一個部分
for(int k=j;k<j+p;++k) //k 嫖取信息
A[k+p]+=op*A[k];
}
后續部分直到下一個黑標題,都有點東西寫錯了,看一樂就好
非常值得一提的是,既然 FWT 又名莫比烏斯變換,我們不妨探究一下他和莫比烏斯反演的關系
我們考慮這個高維前綴和的形式:\(F(S)=\sum _{T\subseteq S} f(T)\)
我們說,與之對應的高維差分:\(f(S)=\sum _{T\subseteq S} (-1)^{|S \backslash T|}F(T)\) 就是莫比烏斯反演
因為我們的高維前綴和實際上解決的是這樣的問題:對於兩個不同的組合對象,從對象 1 中選出 \(S\)
的方案數為 \(f(S)\) ,從對象 2 中選出 \(S\) 的方案書是 \(g(S)\) ,我們想要計算的是 \((f*g)(S)=\sum _{T_1\cup T_2 \subseteq S}f(T_1)g(T_2)\) 的值
發現實際上能產生貢獻的 \((T_1,T_2)\) 需要滿足如下條件:
- \(T_1\subseteq S,T_2 \subseteq S\)
- \(\forall x\in S , s.t. x\in T_1 \cup x\in T_2\)
發現第一個條件是比較喜歡的,因為實際上它意味着所有在 \(T\) 中的元素都在 \(S\) 中,限制比較強烈
而第二個條件涉及到一個 \(or\) 的問題,比較麻煩,我們考慮把這個條件容斥掉
條件的否定是: \(\exist x\in S ,s.t. x\notin T_1 \and x\notin T_2\)
考慮枚舉 \(S^{\prime }\) 使得 \(\exist x\in S^{\prime}\) 滿足上述條件,考慮此時 \(T_1,T_2\) 滿足的條件,有
\(F(S),G(S)\) 就是高維前綴和
所以:並卷積 \((f∗g)(S)=∑_{T_1∪T_2=S}f(T_1 )g(T_2 )\) 的高維前綴和為 \(F⋅G\)
即 \(f∗g\) 為 \(F⋅G\) 的高維差分
那莫比烏斯反演和上面這些有什么關系呢
莫比烏斯反演:
若 $g=1*f $ ,構造函數 \(\mu\) ,使得 \(1*\mu = \in\) ,有 \(f=\mu *g\)
寫開:\(g(n)=\sum _{d|n} f(d)\)
考慮使用多元組來表示一個數,對於一個 \(n=\prod p_i^{a_i}\) ,我們使用 \((a_1,a_2,\dots ,a_r)\) 來表達
重定義 \(\subseteq\) 符號表示對於兩個二元組 \((a_i) ,(b_i)\) ,若 \(\forall i, a_i\le b_i\) ,那么 \((a_i)\subseteq (b_i)\)
那么,可以表示成: \(g(S)=\sum _{T\in S} f(T)\) ,其實就是高維前綴和
所以對應的反演形式,也就是 \(f(n)=\sum _{d|n} \mu (\frac nd)g(d)\) ,可以看做是高維差分 \(f(S)=\sum _{T\subseteq S} (-1)^{|S\backslash T|}g(T)\) ,而對應的 \(\mu(n/d)=(-1)^{|S|-|T|}\)
所以我們得以了解 \(\mu\) 的構造思路!
想象現在我們在做二維差分 ,\(a(i,j)=s(i,j)-s(i-1,j)-s(i,j-1)+s(i-1,j-1)\) ,系數都長成這樣:
同理,考慮在剛剛所提到的表示法之下的系數,以 \(x=2^i 3^j\) 為例子
\(a(x)=s(x)-s(x/2)-s(x/3)+s(x/6)\) ,對應的 \(\mu(1)=1,\mu (2)=-1,\mu(6)=1,\mu(合數)=0\)
由此:
FWTand
懶得寫啦!所以直接擺式子
注意到從某種意義上來說,\(and\) 和 \(or\) 是類似的,以為
\(0|0=1,0|1=1,1|0=1,1|1=1\)
\(0\&0=0,0\&1=0,1\&0=0,1\&1=1\)
void FWT(ll *A,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)
A[k]+=A[k+p]*opt;
}
FWTxor
其實 FWTxor 的推導過程才和 FFT 是最像的,怎么說呢?
顯然有 \(C_0=A_0B_0+A_1B_1\),\(C_1=A_0B_1+A_1B_0\)
FFT 的蝴蝶變換叫做:
FWTxor 直接套用:
然后你發現 \(FWT(A)\times FWT(B)=Merge(FWT(A_0B_0+A_1B_1+A_0B_1+A_1B_0),FWT(A_0B_0+A_1B_1-A_0B_1-A_1B_0))\)
然后你發現前半部分多了 \(A_0B_1+A_1B_0\) ,后面部分需要多了 \(A_0B_0+A_1B_1\) 而且還需要變個號
所以你靈光一現,如此構造出了 IFWT:
\(IFWT(A)=Merge(IFWT(\dfrac{A_0+A_1}2),IFWT(\dfrac{A_0-A_1}2))\)
然后你發現好像是解決了
void fwtxor(int *f,int op){
for(int i=2;i<=len;i<<=1)
for(int p=i>>1,j=0;j<len;j+=i)
for(int k=j;k<j+p;++k){
int x=f[k],y=f[k+p];
f[k]=(x+y)%mod,f[k+p]=(x-y+mod)%mod;
if(op==-1)(f[k]*=inv2)%=mod,(f[k+p]*=inv2)%=mod;
}
}
(細心的讀者可能發現這份代碼和上面的數組不一樣,而且取了模,但其實是一樣的,只是從不同題里面 copy 出來的)
然后你開始探究 FWTxor 的本質(“解析式”)
然后你懵逼了
然后你上網看了一下:
然后你嘗試探究如何從 FWT 的遞推式得到它,
然后你發現你想不明白,但是模擬一下它就是對的,於是你有上網一通亂找,你找到了 pyb‘s ppt 的參考文獻
當你看完了巨佬的文章,你開始嘗試自己推導:
這個時候你又知道這樣一個式子:
其中 \(U\) 代表某一個大小為 \(2^n\) 的全集,\(W\) 是隨便自己給定了一個集合
它的正確性是很顯然的,當 \(W\) 不為空的時候,顯然會出現一些 \(-1\) ,從而使得這個 \(\sum\) 的值小於 \(2^n\)
當且僅當 \(W\) 為空集的時候,才會使得這個 \(\sum=2^n\)
然后,你利用這個式子,進行你的推導
你發現你的推導還需要一點東西,所以你來證明這個:
其中 \(s_i\) 是某個集合,\(T\) 是某個集合。
首先,我們來證明 \(x=2\) 的情況,即:\(|i\cap (j\oplus k)|\equiv|i\cap j|+|i\cap k|\pmod 2\)
(注意,下面沒有采用原博客所使用的方法)
我們直接把集合看成二進制(反正上面的文章也一直把集合和二進制在混用qwq) ,下面簡記 \(popcount\to ppc\)
即證明 \(ppc(i\&(j\oplus k))\equiv ppc(i\&j)+ppc(i\&k)\pmod 2\)
(突然發現由於寫了太多,導致上面出現了奇怪的人稱變化)
設 \(z=i\&(j\oplus k),x=i\&j,y=i\&k\) ,接下來分類討論,對於二進制每一位,都有:
- \(x\) 當前位為 \(0\) ,\(y\) 當前位為 \(0\) 。那么要么是 \(i\) 沒有當前位,要么是 \(j,k\) 均沒有當前位,所以 \(z\) 當前位也是 \(0\)
- \(x\) 當前位為 \(0\) ,\(y\) 當前位為 \(1\) 。那么肯定是 \(j\) 沒有當前位,\(i,k\) 均有當前位,所以 \(z\) 當前位也是 \(1\)
- \(x\) 當前位為 \(1\) ,\(y\) 當前位為 \(0\) 。那么肯定是 \(k\) 沒有當前位,\(i,j\) 均有當前位,所以 \(z\) 當前位也是 \(1\)
- \(x\) 當前位為 \(1\) ,\(y\) 當前位為 \(1\) 。那么肯定是 \(i,j,k\) 均有當前位,\(z\) 當前位為 \(0\) ,在 \(mod\ 2\) 意義下依舊滿足
所以現在我們已經證明了 \(x=2\) 的情況,容易發現 \(n>2\) 的情況可以看成 \(n=2\) 的情況,解決
所以我們現在繼續我們的推式子大業:
順理成章的,我們知道了:
!!!
「CF662C」 Binary Table
CF662C Binary Table - 洛谷 | 計算機科學教育新生態 (luogu.com.cn)
延續做一道黑題寫一篇題解的傳統,我們直接把題解寫在這里面
Statement
有一個 \(n\) 行 \(m\) 列的表格,每個元素都是 \(0/1\) ,每次操作可以選擇一行或一列,把 \(0/1\) 翻轉,即把 \(0\) 換為 \(1\) ,把 \(1\) 換為 \(0\) 。請問經過若干次操作后,表格中最少有多少個 \(1\) 。
\(n\leq 20,m\leq 10^5\)
Solution
注意到 \(𝑛\) 很小
容易得到暴力算法:暴力枚舉第 \(𝑖\) 行是否翻轉,這樣每一行的狀態就已確定,對於每一次完整的行翻轉后,取每一列 \(0/1\) 個數較小的貢獻即可。(因為每一列也可以翻轉)
時間復雜度是 \(𝑂(𝑚 ⋅ 2^𝑛 )\) ,我們考慮優化掉這個 \(m\)
顯然行的操作我們可以狀壓為 \(𝑥\),(二進制為 \(1\) 的位表示那一行要翻轉)
每一列的的狀態值也可以壓縮:
- 用 \(𝐴[𝑖]\) 表示在原表列中, \(𝑖\) 這個狀態數量
- 用 \(𝐵[𝑖]\) 表示 \(𝑖\) 這個狀態最小 \(1\) 的個數(即 \(\min(popcount(i), n-ppc(i)\))
當確定了行的操作為 \(x\) ,那么所有狀態為 \(i\) 的列的貢獻為:\(A[i]B[i\oplus x]\)
令 \(j=i\oplus x\) ,那么 \(x=i\oplus j\)
所以 \(ans_x=\sum_{i\oplus j=x}A[i]B[j]\) ,直接上 FWT 即可
復雜度:\(O(\max(n2^m,nm))\)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = (1<<21);
const int M = 1e5+5;
char s[25][M];
int a[N],b[N];
int n,m,ans=1e18;
void fwtxor(int *f,int op){
for(int i=2;i<=(1<<n);i<<=1)
for(int p=i>>1,j=0;j<(1<<n);j+=i)
for(int k=j;k<j+p;++k){
int x=f[k],y=f[k+p];
f[k]=x+y,f[k+p]=x-y;
if(op==-1)f[k]/=2,f[k+p]/=2;
}
}
signed main(){
scanf("%lld %lld",&n,&m);
for(int i=1;i<=n;++i)scanf("%s",s[i]+1);
for(int i=1,k;k=0,i<=m;++i,a[k]++)
for(int j=1;j<=n;++j)k=k<<1|(s[j][i]-'0');
for(int i=0,t;i<(1<<n);++i)
t=__builtin_popcount(i),b[i]=min(t,n-t);
fwtxor(a,1),fwtxor(b,1);
for(int i=0;i<(1<<n);++i)a[i]*=b[i];
fwtxor(a,-1);
for(int i=0;i<(1<<n);++i)ans=min(ans,a[i]);
printf("%lld\n",ans);
return 0;
}
子集卷積
寫不動了!!
https://www.luogu.com.cn/problem/P6097
所謂子集卷積,聽着很高級,其實也就那樣(就最基本的而言),好像有叫做集合占位冪級數什么的
它用來處理求出這樣一個 \(c\):
我們很容易處理第一個限制 \(i \cap j=k\),直接 FWTand 即可。
對於第二個限制 \(i \cap j=\varnothing\Leftrightarrow |i|+|j|=|i\cap j|\),所以我們不妨再開一維記錄集合中的元素個數
也就是說設 \(f_{i,j} = a_j[|j|=i],g_{i,j}=b_j[|j|=i]\),把他們卷起來,\(h_{i,j}=\sum_{k=0}^i \sum_{l|r=j}f_{k,l}g_{i-k,r}\)
最后的答案即為: \(c_i=h_{|i|,i}\) ,因為 \(popcount(i)+popcount(j)\ge popcount(i|j)\) ,所以相等的時候取到的就是正確的值
(即 \(|S|+|T|=|S|T|+|S\&T|\) )
#include<bits/stdc++.h>
using namespace std;
const int N = 1<<21;
const int mod = 1e9+9;
int read(){
int s=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+ch-'0',ch=getchar();
return s*w;
}
int a[21][N],b[21][N],c[21][N];
int n,len;
void fwt(int *f,int op){
for(int i=2;i<=len;i<<=1)
for(int p=i>>1,j=0;j<len;j+=i)
for(int k=j;k<j+p;++k)
(f[k+p]+=op*f[k])%=mod;
}
signed main(){
n=read(),len=1<<n;
for(int i=0;i<len;++i)a[__builtin_popcount(i)][i]=read();
for(int i=0;i<len;++i)b[__builtin_popcount(i)][i]=read();
for(int i=0;i<=n;++i)fwt(a[i],1),fwt(b[i],1);
for(int i=0;i<=n;++i)
for(int j=0;j<=i;++j)
for(int k=0;k<len;++k)
(c[i][k]+=(long long)a[j][k]*b[i-j][k]%mod)%=mod;
for(int i=0;i<=n;++i)fwt(c[i],-1);
for(int i=0;i<len;++i)printf("%d ",(c[__builtin_popcount(i)][i]+mod)%mod);
return 0;
}
所謂集合占位冪級數,是這樣的形式:
擴展:https://www.luogu.com.cn/blog/user7035/zi-ji-juan-ji-ji-ji-gao-ji-yun-suan (我不會,等我長大后再學習)
[WC2018] 州區划分
WC2018 州區划分 - 洛谷 | 計算機科學教育新生態 (luogu.com.cn)
延續做一道黑題寫一篇題解的傳統,我們直接把題解寫在這里面
Statement
由於原題面已經概括得很好了,所以這里直接貼圖:
\(n\leq 21,m\leq n^2/2,p\le 2 ,w_i\le 100\)
Solution
容易考慮狀壓 DP,設 \(f[i][j]\) 表示划分了 \(i\) 個州,選出的城市的集合為 \(j\) 的貢獻,那么
其中,\(sum[s]\) 表示集合 \(s\) 的 \(w\) 的和,\(chk[s]\) 表示集合 \(s\) 是否可以獨立成州
所謂獨立成州的條件其實可以轉化為 圖不連通/存在奇度數點,我們容易在 \(O(2^nm)\) 左右的時間暴力預處理出 \(sum,chk\)
設 \(g[k]=sum[k]chk[k]\) ,那么:
發現后面的那一坨其實就是子集卷積,所以復雜度 \(O(n^22^n)\) 解決
Code
#include<bits/stdc++.h>
#define int long long
#define ppc(x) __builtin_popcount(x)
using namespace std;
const int mod = 998244353;
const int N = 22;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
int s=0,w=1; char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
int u[N*N],v[N*N],w[N],fa[N],deg[N],inv[(1<<N)+5];
int f[N][(1<<N)+5],g[N][(1<<N)+5];
int n,m,p;
bool vis[N];
void input(){
n=read(),m=read(),p=read();
for(int i=1;i<=m;++i)u[i]=read(),v[i]=read();
for(int i=1;i<=n;++i)w[i]=read();
}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
bool merge(int u,int v){
u=find(u),v=find(v);
return u==v?0:fa[u]=v;//////////////
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)res=res*a%mod;
a=a*a%mod,b>>=1;
}
return res;
}
void preparation(){
for(int i=0,sum,cnt,flag;sum=cnt=flag=0,i<(1<<n);++i){
for(int j=1;j<=n;++j)fa[j]=j,vis[j]=false,deg[j]=0;
for(int j=0;j<n;++j)if(i&(1<<j))vis[j+1]=true,sum+=w[j+1],cnt++;
for(int j=1;j<=m;++j)if(vis[u[j]]&&vis[v[j]])
merge(u[j],v[j])&&(cnt--,1),deg[u[j]]++,deg[v[j]]++;
flag|=cnt!=1,cnt=0;
for(int j=1;j<=n;++j)cnt+=(deg[j]&1);
flag|=cnt!=0,g[ppc(i)][i]=flag*ksm(sum,p);
inv[i]=ksm(ksm(sum,mod-2),p);
}
}
void fwtor(int *f,int op){
for(int i=2;i<=(1<<n);i<<=1)
for(int j=0,p=i>>1;j<(1<<n);j+=i)
for(int k=j;k<j+p;++k)(f[k+p]+=(op*f[k]+mod)%mod)%=mod;
}
void work(){
for(int i=0;i<=n;++i)fwtor(g[i],1);
f[0][0]=1,fwtor(f[0],1);
for(int i=1;i<=n;++i){
for(int j=0;j<i;++j)//j!=i
for(int k=0;k<(1<<n);++k)
(f[i][k]+=f[j][k]*g[i-j][k]%mod)%=mod;
fwtor(f[i],-1);
for(int j=0;j<(1<<n);++j)
f[i][j]=(ppc(j)==i)*(f[i][j]*inv[j]%mod);
if(i^n)fwtor(f[i],1);
}
}
void output(){
printf("%lld\n",f[n][(1<<n)-1]);
}
signed main(){
input();
preparation();
work();
output();
return 0;
}
「CF1034E」Little C Loves 3 III
Statement
給定 \(n\) 和長度為 \(2^n\) 的數列 \(a_{0},a_{1}...a_{2^n-1}\) 和 \(b_{0},b_1...b_{2^n-1}\),保證每個元素的值屬於$ [0,3]$
生成序列 \(c\),對於 \(c_i\),有:
\(c_i=\sum_{j|k=i,j\&k=0} a_j\times b_k\)
求 \(c_{0},c_1...c_{2^n-1}\),答案對 \(4\) 取模。
\(n\le 21\),時限 \(\rm 1s\)
Solution
顯然,暴力 \(O(n^22^n)\) 的子集卷積不可過,考慮利用 \(\bmod 4\) 的性質
普通的,子集卷積為了取到正確的值,采用的方法可以理解為 DP 多設一維狀態 \({|S|}\) ,使得我們加入了 \(z^{|S|}\) 這一項
這里,我們讓 \(a_S=a_S\times 4^{|S|},b_T=b_T\times 4^{|T|}\) (鬼知道是怎么想到的!)
用 FWT 求出 \(c\) 后,\(x^{|S|}\) 項系數對 \(4^{|S|+1}\) 取模,再除以一個 \(4{|S|}\) 即可
解釋:
集合占位冪級數是這樣的形式: \(\sum_S (f_S z^{|S|}x^S+\sum_{i>|S|}\sigma_{i,S}z^ix^S)\) 其中 \(z\) 的取值是任意的, $\sigma $ 一般都取 \(\rm 0\) ,\(\sigma\) 意義是在於可能會在 \(S\) 位置瞎 jb 轉上一些不計入答案的貢獻
而當我們把 \(z\) 取到 \(\rm 4\) , \(\bmod 4^{|S|+1}\) 干掉了后面那個 \(\sum\) ,除 \(4^{|S|}\) 相當於還原
於是這樣快樂 FWT 即可,\(O(n2^n)\)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
char s[1<<21|5],t[1<<21|5];
int f[1<<21|5],g[1<<21|5];
int n;
void fwt(int *f,int op){
for(int i=2;i<=(1<<n);i<<=1)
for(int p=i>>1,j=0;j<(1<<n);j+=i)
for(int k=j;k<j+p;++k)f[k+p]+=op*f[k];
}
signed main(){
scanf("%lld%s%s",&n,s,t);
for(int i=0;i<(1<<n);++i)f[i]=(s[i]&15ll)<<(__builtin_popcount(i)<<1);fwt(f,1);
for(int i=0;i<(1<<n);++i)g[i]=(t[i]&15ll)<<(__builtin_popcount(i)<<1);fwt(g,1);
for(int i=0;i<(1<<n);++i)f[i]*=g[i]; fwt(f,-1);
for(int i=0;i<(1<<n);++i)putchar(f[i]>>(__builtin_popcount(i)<<1)&3|48);
return 0;
}
完結撒花!!!!✿✿ヽ(°▽°)ノ✿