• Nim积解法小结


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

    本文参考了 OwaskiGameTheory 的课件。

    定义

    我们对于一些二维 (mathrm{Nim}) 游戏(好像更高维也行),可以拆分成两维单独的 (mathrm{Nim}) 然后求 (mathrm{Nim}) 积。

    定义为

    [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) 的一个小表。

    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

    性质

    运算的性质

    观察此表,可以显然的得出:

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

    (0) 与所有数做 (mathrm{Nim}) 积仍然为 (0)(1) 仍然是单位元,并且满足交换律。

    不会证明的两个结论:

    [egin{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) 看作 ( imes) 以及 (oplus) 看做 (+) )。

    费马数的一些运算性质

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

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

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

    2. 一个 ( ext{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 ext{ 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 ext{ 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)) (注意费马数的 ( imes)(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 ext{ 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 ext { xor } y)(x ext { and } y) 的情况分开。

    [egin{aligned} 2^x otimes 2^y &= (otimes_{i in {x ext{ xor } y}} 2^{2^i}) oplus (otimes_{i in {x ext{ and } y}} frac{3}{2} 2^{2^i})\ &= (prod_{i in {x ext{ xor } y}} 2^{2^i}) otimes (otimes_{i in {x ext{ 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(Tnlog x log y)) 的。

  • 相关阅读:
    定时器
    js中script的上下放置区别 , Dom的增删改创建
    函数声明与应用
    常规选择器
    表格的制作
    流程控制
    For循环
    洛谷P1419寻找段落
    洛谷P1021邮票面值设计
    洛谷P3119草鉴定
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/10507030.html
Copyright © 2020-2023  润新知