快速沃爾什變換(FWT)快速莫比烏斯變換(FMT)
雖然一點不懂,但是看著程式碼短得感人,背過就好了吧。
或
\[FWT(A) = (FWT(A_0), FWT(A_1 + A_0)) \]
\[IFWT(A) = (IFWT(A_0), IFWT(A_1 - A_0)) \]
與
\[FWT(A) = (FWT(A_0 + A_1), FWT(A_1)) \]
\[IFWT(A) = (IFWT(A_0 - A_1), IFWT(A_1)) \]
異或
\[FWT(A) = (FWT(A_0 + A_1), FWT(A_0 - A_1)) \]
\[FWT(A) = (FWT(\frac{A_0 + A_1}{2}), FWT(\frac{A_0 - A_1}{2})) \]
程式碼實現
記憶
有些東西不用全靠硬背,可以有技巧地背
或
進行或運算,通常數會往大里走,所以較高位的數要加上較低位的數 (霧)
與
與 與 或 相反,通常數會往小裡走,於是 是較低位的數加上較高位的數 (大霧)
異或
有點特殊? 有點像 FFT?那就記成 FFT 的形式就好了吧...
IFWT
IFWT 就是把 FWT 給還原一下就好了吧。
之前某位置加上了一個數,而現在那個數還沒變 (大霧),那麼就把那個數減掉就好了吧。
(對於異或)找不到之前那個數?沒關係,可以列個方程解出來 (大霧)
注意!!
-
FWT不用倍長,不用蝴蝶變換
-
FWT裡面還是儘量用<=比較安全,只要空間足夠就沒啥問題。特別注意一點:選取limit時要while (limi <= n)!!這時候一定要小於等於,否則不全!!
-
其餘各項同ntt裡面的注意
補充說明:
對於或卷積:(\(FWT:f[] -> f'[]\))
\[f'[i]=\sum_{j|i=i}f[j] \]
發現 \(f'[i]\) 為 \(i\) 的子集的權值和,因此可以做一些子集問題,如:P3175 [HAOI2015]按位或
子集卷積
給定\(a, b\)陣列,令:
\[c_k = \sum_{i~and~j=0,i~or~j=k}a_i * b_j \]
即在 \(k\) 中找到兩個互不相交的子集 \(i,j\),計算 \(a_i * b_j\) 的總和
如果不要求互不相交,那麼就是個 FWT_OR 的板子題了。如果要求互不相交,那麼只需加上限制 \(|i| + |j| = |k|\)
於是,我們可以列舉 \(|i|,|j|,|k|\),然後將所有大小為 \(|i|,|j|\) 的 \(|a_i|,|b_j|\) 的總和乘一塊加給 \(c_k\),然後再將 \(c\) 陣列捲回去,即為答案。
例題:P6570 [NOI Online #3 提高組]優秀子序列(民間資料)(洛谷資料可以被暴力水過,隨機資料下本機也可過,但是CCF的評測機太慢,過不去)
應用
4589: Hard Nim
經典Nim遊戲(輪流取石子,無法操作者為負)。
n堆石子滿足每堆石子的初始數量是不超過m的質數。
求先手必敗局面數。
n <= 1e9, m <= 5e4
最樸素的方法自然是列舉每一種方案,判斷其合法不合法。
\[\sum_{i=1}^{m}\sum_{j=1}^{m}\sum_{k=1}^m...(n~sigma)...\sum_{l=1}^m[i,j,k,...,l=prime,i~xor~j~xor~k~xor~...~xor~l=0] \]
複雜度:O(\(nm^n\))
當只有兩個sigma(n=2)的時候是:
\[\sum_{i=1}^m\sum_{j=1}^m[i,j=prime,i~xor~j=0] \]
即
\[\sum_{i~xor~j=0}{[i,j=prime]} \]
是異或卷積的形式。
(思維逐漸混亂)
根據FFT相關計數題目的經驗,我們可以用權值陣列。即 \(a[i]\) 表示異或值為 \(i\) 有多少種情況。然後就可以用FWT了。
考慮類似快速冪的方法,以快速冪的格式,把 \(a[~]\) 當作 \(x\),做多項式快速冪,最終答案就是 \(a[0]\)。複雜度大概\(O(m~logm~logn\))(有點懸?)
一想到多項式快速冪,我們就成功的走向了大彎路,甚至複雜度都有點保不住。不要忘記FFT,NTT,FWT都是藉助點值表示加速的本質。在轉化成點值表示以後,仍支援交換律,結合律等,因此可以直接把每個點值做快速冪。複雜度\(O(m~logm~+~m~logn)\)
\(Code:\)
limi = 1;
while (limi <= m) limi <<= 1;
for (register int i = 0; i <= m; ++i) A[i] = (!depri[i]);
FWT_xor(A, 1);
for (register int i = 0; i <= limi; ++i) A[i] = quickpow(A[i], n);
FWT_xor(A, -1);
printf("%lld\n", A[0]);
CF662C Binary Table
有一個 n 行 m 列的表格,每個元素都是 0/1 ,每次操作可以選擇一行或一列,把 0/1 翻轉,即把 0 換為 1 ,把 1 換為 0 。請問經過若干次操作後,表格中最少有多少個 1 。
\(n<=20, m<=1e5\)
考慮列舉行翻轉情況為State。設一開始第 i 列的情況為 \(S_i\),則對於每個 \(State\) 來說,答案為(\(F_s\)表示某一列狀態為 \(s\) 的最大貢獻(1個數):
\[\sum_{i=1}^m{F_{State~xor~S_i}} \]
轉換列舉物件: 列舉 對行操作後的列狀態 X(方便直接使用 \(F_X\) 統計答案) 和 \(S_i\) (\(Q_s\) 表示狀態為 \(S\) 的列有多少個):
\[\sum_{X=0}^{2^n}\sum_{S = 0}^{2^n}{[State~xor~S = X]F_{X}* Q_{S}} \]
稍作變換:
\[\sum_{X=0}^{2^n}\sum_{S = 0}^{2^n}{[State = X~xor~S]F_{X}* Q_{S}} \]
即:
\[\sum_{X~xor~S=State}{F_X * Q_S} \]
然後預處理出 F 和 Q ,FWT即可。
練習題
附
FWT模板(除錯用)
inline void FWT_or(ll *a, int type) {
for (register int i = 1; i < limi; i <<= 1) {
for (register int j = 0; j < limi; j += (i << 1)) {
for (register int p = 0; p < i; ++p) {
a[i + j + p] = (a[i + j + p] + a[j + p] * type) % P;
if (a[i + j + p] < 0) a[i + j + p] += P;
}
}
}
}
inline void sol_or() {
memcpy(tp1, A, sizeof(A));
memcpy(tp2, B, sizeof(B));
FWT_or(tp1, 1); FWT_or(tp2, 1);
for (register int i = 0; i < limi; ++i) C[i] = tp1[i] * tp2[i] % P;
FWT_or(C, -1);
for (register int i = 0; i < limi; ++i) printf("%lld ", C[i]);
puts("");
}
inline void FWT_and(ll *a, int type) {
for (register int i = 1; i < limi; i <<= 1) {
for (register int j = 0; j < limi; j += (i << 1)) {
for (register int p = 0; p < i; ++p) {
a[j + p] = (a[j + p] + a[i + j + p] * type) % P;
if (a[j + p] < 0) a[j + p] += P;
}
}
}
}
inline void sol_and() {
memcpy(tp1, A, sizeof(A));
memcpy(tp2, B, sizeof(B));
FWT_and(tp1, 1); FWT_and(tp2, 1);
for (register int i = 0; i < limi; ++i) C[i] = tp1[i] * tp2[i] % P;
FWT_and(C, -1);
for (register int i = 0; i < limi; ++i) printf("%lld ", C[i]);
puts("");
}
inline void FWT_xor(ll *a, int type) {
for (register int i = 1; i < limi; i <<= 1) {
for (register int j = 0; j < limi; j += (i << 1)) {
for (register int p = 0; p < i; ++p) {
ll nx = a[j + p], ny = a[i + j + p];
a[j + p] = (nx + ny) % P;
a[i + j + p] = (nx - ny + P) % P;
if (type == -1) {
a[j + p] = a[j + p] * inv2 % P;
a[i + j + p] = a[i + j + p] * inv2 % P;
}
}
}
}
}
inline void sol_xor() {
memcpy(tp1, A, sizeof(A));
memcpy(tp2, B, sizeof(B));
FWT_xor(tp1, 1); FWT_xor(tp2, 1);
for (register int i = 0; i < limi; ++i) C[i] = tp1[i] * tp2[i] % P;
FWT_xor(C, -1);
for (register int i = 0; i < limi; ++i) printf("%lld ", C[i]);
puts("");
}