1. 程式人生 > 實用技巧 >CSUST 2006-Simple Inversions(動態逆序對-分塊)

CSUST 2006-Simple Inversions(動態逆序對-分塊)

雖然一點不懂,但是看著程式碼短得感人,背過就好了吧。

\[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]按位或

子集卷積

P6097 【模板】子集卷積

給定\(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

先看題解 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即可。

練習題

P3175 [HAOI2015]按位或

A國的貿易

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("");
}