由於某毒瘤出題人
redbag不得不學習一下這個史詩毒瘤算法。本文參考了 Owaski 的 GameTheory 的課件。
定義
我們對於一些二維 \(\mathrm{Nim}\) 游戲(好像更高維也行),可以拆分成兩維單獨的 \(\mathrm{Nim}\) 然后求 \(\mathrm{Nim}\) 積。
定義為
其中 \(\otimes\) 定義為 \(\mathrm{Nim}\) 積,\(\oplus\) 定義為異或。
以下是對於 \(x, y \le 4\) 的一個小表。
0 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 2 | 3 | 4 |
2 | 0 | 2 | 3 | 1 | 8 |
3 | 0 | 3 | 1 | 2 | 12 |
4 | 0 | 4 | 8 | 12 | 6 |
性質
運算的性質
觀察此表,可以顯然的得出:
即 \(0\) 與所有數做 \(\mathrm{Nim}\) 積仍然為 \(0\) , \(1\) 仍然是單位元,並且滿足交換律。
不會證明的兩個結論:
就是說滿足乘法交換律,和乘法分配率(把 \(\otimes\) 看作 \(\times\) 以及 \(\oplus\) 看做 \(+\) )。
費馬數的一些運算性質
經過數學家的艱苦卓絕的努力,我們有兩個十分強大的運算法則。
定義 \(\text{Fermat 2-power}\) 為 \(2^{2^n}\) ,其中 \(n \in \mathbb N\) ,設其為 \(a\) 。
-
一個 \(\text{Fermat 2-power}\) 與任意小於它的數的 \(\mathrm{Nim}\) 積為一般意義下乘法的積,即 \(a \otimes x = a \times x~(x < a)\) 。
-
一個 \(\text{Fermat 2-power}\) 與自己的 \(\mathrm{Nim}\) 積為自己的 \(\displaystyle \frac 32\) 倍,即 \(\displaystyle a \otimes a = \frac 3 2 a = a \oplus \frac a 2\) 。
算法解決
注意暴力求 \(\mathrm{Nim}\) 積是 \(\mathcal O((xy)^2)\) 的,我們可以利用一些性質在 \(\mathcal O(\log x \log y)\) 的時間內解決。
對於任意 \(x, y\) 的解法
我們設 \(f(x, y) = x \otimes y\) ,我們特判 \(x \text{ or } y = 0, 1\) 的情況后,可以考慮拆出 \(x, y\) 的每個二進制位單獨算。
就是設 \(g(x, y) = 2^x \otimes 2^y\) ,那么 \(f(x, y) = \oplus_{x' \in x, y' \in y} g(x', y')\) 。
對於 \(2^x \otimes 2^y\) 的解法
這一段是 zhou888 教我的,太恐怖啦 %%%
那么我們問題就轉化為求 \(g(x, y)\) 了。
我們考慮把 \(x, y\) 的二進制位拆出來,變成一個個費馬數,然后利用性質處理。
考慮 從高到低 依次考慮 \(x, y\) 的每一位,如果這位都為 \(0\) 我們顯然可以忽略。
\(x \text{ and } y\) 的情況
假設全都為 \(1\) 那么對於這一位 \(2^u\) 我們設 \(M = 2^{2^u}, A = 2^{x - 2^u}, B = 2^{y - 2^u}\) ,那么有 \(A, B < M\) 。
那么我們的答案其實就是 \(ans = (M \otimes A) \otimes (M \otimes B)\) (注意費馬數的 \(\times\) 和 \(\otimes\) 是一樣的)即 $ (M \otimes M) \otimes (A \otimes B)$ ,化簡一下答案其實就是 \(\displaystyle \frac{3}{2} M \otimes (A \otimes B)\) 。
那么此時我們把 \(2^x, 2^y\) 都去掉最高的一位 \(u\) 變成 \(A, B\) ,繼續向低位遞歸。
\(x \text{ xor } y\) 的情況
假設一個為 \(1\) 一個為 \(0\) ,同樣我們設這位為 \(2^u\) ,假設 \(x\) 此位為 \(1\) ,那么有 \(M = 2^{2^u}, A = 2^{x - 2^u}, B = 2^y\) 。
那么答案的形式為 \(ans = (M \otimes A) \otimes B\) 也就是 \(M \otimes (A \otimes B)\) 。類似的,我們去掉最高位,然后不斷向下推。
討論完上面兩種情況,我們可以寫一下表達式。
我們顯然可以利用交換律把 \(x \text { xor } y\) 和 \(x \text { and } y\) 的情況分開。
那么對於前者可以直接算,后面利用 \(f\) 遞歸算就行了。
復雜度不難發現只會遍歷兩個所有二進制位,也就是單次為 \(\mathcal O(\log^2 x)\) 。
代碼實現
網上的那種推導以及實現方式似乎都有些問題,似乎是其中一個費馬數的地方沒有保證 \(<\) ,小的不會錯,大的會有些問題。
所以我參考了 zhou888 的代碼實現。
#define Resolve(i, x) for (int u = (x), i = 0; (1ll << i) <= u; ++ i) if (u >> i & 1)
ll f(ll x, ll y);
ll g(int x, int y) {
if (!x || !y) return 1ll << (x | y);
if (~ tab[x][y]) return tab[x][y];
ll res = 1;
Resolve(i, x ^ y) res <<= (1 << i);
Resolve(i, x & y) res = f(res, 3ll << ((1 << i) - 1));
return tab[x][y] = res;
}
ll f(ll x, ll y) {
if (!x || !y) return x | y;
if (x == 1 || y == 1) return max(x, y);
ll res = 0;
Resolve(i, x) Resolve(j, y) res ^= g(i, j);
return res;
}
例題
HDU3404 Switch lights
題意
在一個二維平面中,有 \(n\) 個燈亮着並告訴你坐標,每回合需要找到一個矩形,這個矩形 \((x,y)\) 坐標最大的那個角落的點必須是亮着的燈,然后我們把四個角落的燈狀態反轉,不能操作為敗。
\(T \le 100, n \le 1000, x, y \le 10000\)
題解
\(\mathrm{Turning~Corners}\) 是裸的二維 \(\mathrm{Nim}\) 問題,直接上模板就好了。
復雜度是 \(\mathcal O(Tn\log x \log y)\) 的。