Nim积解法小结

因为某毒瘤出题人 redbag 不得不学习一下这个史诗毒瘤算法。算法

本文参考了 OwaskiGameTheory 的课件。学习

定义

咱们对于一些二维 \(\mathrm{Nim}\) 游戏(好像更高维也行),能够拆分红两维单独的 \(\mathrm{Nim}\) 而后求 \(\mathrm{Nim}\) 积。spa

定义为code

\[ x \otimes y = \mathrm{mex}\{(a \otimes b) \oplus (a \otimes y) \oplus (x \otimes b), 0 \le a < x, 0 \le b < y\} \]递归

其中 \(\otimes\) 定义为 \(\mathrm{Nim}\) 积,\(\oplus\) 定义为异或。游戏


如下是对于 \(x, y \le 4\) 的一个小表。get

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

性质

运算的性质

观察此表,能够显然的得出:数学

\[ \begin{aligned} x \otimes 0 &= 0 \otimes x = 0\\ x \otimes 1 &= 1 \otimes x = x\\ x \otimes y &= y \otimes x \end{aligned} \]it

\(0\) 与全部数作 \(\mathrm{Nim}\) 积仍然为 \(0\)\(1\) 仍然是单位元,而且知足交换律。table

不会证实的两个结论:

\[ \begin{aligned} x \otimes (y \otimes z) &= (x \otimes y) \otimes z\\ x \otimes (y \oplus z) &= (o \otimes y) \oplus (x \otimes z) \end{aligned} \]

就是说知足乘法交换律,和乘法分配率(把 \(\otimes\) 看做 \(\times\) 以及 \(\oplus\) 看作 \(+\) )。

费马数的一些运算性质

通过数学家的坚苦卓绝的努力,咱们有两个十分强大的运算法则。

定义 \(\text{Fermat 2-power}\)\(2^{2^n}\) ,其中 \(n \in \mathbb N\) ,设其为 \(a\)

  1. 一个 \(\text{Fermat 2-power}\) 与任意小于它的数的 \(\mathrm{Nim}\) 积为通常意义下乘法的积,即 \(a \otimes x = a \times x~(x < a)\)

  2. 一个 \(\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\) 的二进制位拆出来,变成一个个费马数,而后利用性质处理。

\[ 2^x \otimes 2^y = (\otimes_{x' \in x} 2^{2^{x'}}) \otimes (\otimes_{y' \in y} 2^{2^{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\) 的状况分开。

\[ \begin{aligned} 2^x \otimes 2^y &= (\otimes_{i \in \{x \text{ xor } y\}} 2^{2^i}) \oplus (\otimes_{i \in \{x \text{ and } y\}} \frac{3}{2} 2^{2^i})\\ &= (\prod_{i \in \{x \text{ xor } y\}} 2^{2^i}) \otimes (\otimes_{i \in \{x \text{ and } y\}} \frac{3}{2} 2^{2^i}) \end{aligned} \]

那么对于前者能够直接算,后面利用 \(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)\) 的。

相关文章
相关标签/搜索