博弈论乱写2:Nimber

需要一点代数能力,应该不高。

Nimber

在博弈论的学习过程中,我们发现了 Nim 游戏的重要性,实际上,所有 ICG 游戏都等价于若干尼姆游戏的和。

我们假设只有一堆的尼姆游戏的值为 \(x\),那么两个尼姆游戏的和就等于 \(x\operatorname{xor}y\)。这个性质不难推广到多个尼姆游戏间,我们把这个描述了游戏必胜状态的值叫做 Nimber,或者尼姆数。

Nim 和

定义 \(a\oplus b\) 表示大小为 \(a\)\(b\) 的两个 Nim 游戏的和,定义如下:

\(a\oplus b=\operatorname{mex}(\{a\oplus b^\prime\mid 0\le b^\prime<b\}\cup\{a^\prime\oplus b\mid 0\le a^\prime<a\})\)

实际上,我们知道 \(a\oplus b=a\operatorname{xor}b\)。(虽然 \(\operatorname{mex}\) 不是唯一的合法运算,但是用 \(\operatorname{mex}\) 时,\(\oplus\)\(\operatorname{xor}\) 是等价的)

Nim 积

Nim 积的定义

既然 Nimber 的加法和自然数的加法满足许多共性,比如都满足交换律、结合律,我们期望 \(\oplus\) 运算可以这有对应的乘法 \(\otimes\),使得 \(\oplus\) 关于 \(\otimes\) 有分配律,这样 Nimebr 和 \((\oplus,\otimes)\) 就可以构成一个域。

在多项式的学习中,我们都知道,\(\operatorname{deg}(F+G)=\max(\operatorname{deg}(F),\text{deg}(G)),\operatorname{deg}(FG)=\operatorname{deg}(F)+\operatorname{deg}(G)\)。也就是说,一般的乘法对应了维度变化的操作,我们期望 \(\otimes\) 也满足这个性质。

于是引出了 \(k\) 维 Nim 游戏:

在一个 \(k\) 维空间上,有若干个黑点,\((a_{1,i},a_{2,i}\ldots a_{k,i})\),每次选择一个 \((b_1,b_2\ldots b_k),(a_{1,i},a_{2,i}\ldots a_{k,i})\),满足 \(0\le b_1< a_{1,i},0\le b_2<a_{2,i}\cdots 0\le b_k<a_{k,i}\)。将 \(k\) 维立方体 \((b_1,b_2\ldots b_k)-(a_{1,i},a_{2,i}\ldots a_{k,i})\) 的所有端点反色。比如矩形有 \(4\) 个端点,立方体有 \(8\) 个端点,以此类推,\(k\) 维立方体有 \(2^k\) 个端点。

显然,\(k=1\) 就是取石子游戏的模型。

和多项式乘法不同,高维 Nim 游戏十分困难,我们希望 \(\otimes\) 对应的是降维的操作。

直接给出 \(k=2\) 时的定义:

\[a\otimes b=\operatorname{mex}\{(a\otimes y)\oplus(x\otimes b)\oplus(a\otimes b)|a<x,b<y\} \]

发现结果显然是 \(1\) 维的,于是可以把 \(k\) 个维度依次乘起来得到最后的结果。上式可以理解为关于 \(\operatorname{xor}\) 的容斥。

让人惊喜的是,\(\otimes\) 满足许多优美的性质:

\(x\otimes0=0\)

\(x\otimes1=x\)

\(x\otimes y=y\otimes x\)

\(x\otimes y\otimes z=x\otimes (y\otimes z)\)

\(x\otimes(y\oplus z)=x\otimes y\oplus x\otimes z\),这里我们认为 \(\otimes\) 的优先级高于 \(\oplus\)

上面的性质用定义不难验证。

不过我们目前还没有和 \(a\oplus b=a\operatorname{xor}b\) 一样简单的方法计算 \(a\otimes b\) 的值。为了方便我们的计算,可以把后文中的 \(\oplus\) 理解为异或运算,但应当记住 \(\oplus\) 表示的是 Nim 和,异或只是计算的方法。

Nim 积的运算

\(f_k=2^{2^k}\) 为费马数。由前人的努力,我们知道了:(后面的 \(\times,+\) 就是自然数的对应运算)

\[\begin{align*} f_k>a\Rightarrow a\otimes f_k&=a\times f_k\\ f_k\otimes f_k&=\dfrac 32\times f_k\\ n\ne m,f_n\otimes f_m&=2^{2^n+2^m} \end{align*} \]

\(f_k\) 关于 \(k\) 的增速是很快的,有 \(f_6=2^{64}\),也就是说在实际应用中你几乎见不到 \(k>6\) 的情况。故之后我们只考虑 \(x\in[0,2^{2^k})\)\(x\),这里的 \(k\) 认为是小常数。(严格的说,\(k=O(\log\log V)\)

考虑按位分治,设 \(x=af_{k-1}+b,y=cf_{k-1}+d\)。(\(a,c<f_{k-1}\)

显然有 \(af_{k-1}+b=af_{k-1}\operatorname{xor}b=af_{k-1}\oplus b\)\(c,d\) 同理。

因为 \(a<f_{k-1}\),所以 \(af_{k-1}=a\otimes f_{k-1}\)\(c\) 同理。

再设 \(n=2^{k-1}\),则 \(2^n=f_{k-1}\)

那么有:

\[\begin{align*} x\otimes y&=(af_{k-1}\oplus b)\otimes(cf_{k-1}\oplus d)\\ &=(af_{k-1}\otimes cf_{k-1})\oplus(af_{k-1}\otimes d)\oplus(b\otimes cf_{k-1})\oplus(b\otimes d)\\ &=[a\otimes c\otimes (f_{k-1}\otimes f_{k-1})]\oplus [(a\otimes d \oplus b\otimes c)\otimes f_{k-1}]\oplus (b\otimes d)\\ &=[a\otimes c\otimes (2^n\oplus 2^{n-1})]\oplus [(a\otimes d \oplus b\otimes c)\otimes 2^n]\oplus (b\otimes d)\\ &=(a\otimes c\oplus a\otimes d \oplus b\otimes c)\otimes 2^n\oplus(a\otimes c)\otimes 2^{n-1}\oplus(b\otimes d)\\ &=(a\otimes c\oplus a\otimes d \oplus b\otimes c)\otimes f_{k-1}\oplus(a\otimes c)\otimes 2^{n-1}\oplus(b\otimes d) \end{align*} \]

此时只需要计算 \(a\otimes c,a\otimes d,b\otimes c,b\otimes d,(a\otimes c)\otimes 2^{n-1}\)\(5\) 项,可以做到 \(O(5^k)\)

代码如下:

const u32 map_size=256;
u32 mp[map_size][map_size];
inline u32 nim_mul(u32 x,u32 y)
{
    if(x<y) swap(x,y);
    if(!y) return 0u;
    if(y==1) return x;
    if(x<map_size&&mp[x][y]) return mp[x][y];
    ull t=2;
    u32 len=1;
    while(t*t<=x) t=t*t,len<<=1;
    u32 a=x>>len,b=x&(t-1),c=y>>len,d=y&(t-1);
    u32 c1=nim_mul(a,c);
    u32 c2=nim_mul(a,d)^nim_mul(b,c);
    u32 c3=nim_mul(b,d);
    if(x<map_size) return mp[x][y]= ((c1^c2)*t) ^ c3 ^ nim_mul(t>>1u,c1);
    return ((c1^c2)*t) ^ c3 ^ nim_mul(t>>1u,c1);
}

注意 \(\oplus\) 运算的性质,有 \(a\oplus b\oplus b=a\),于是可以进一步优化:

\[\begin{align*} x\otimes y&=(a\otimes c\oplus a\otimes d \oplus b\otimes c)\otimes f_{k-1}\oplus(a\otimes c)\otimes 2^{n-1}\oplus(b\otimes d)\\ &=[(a\oplus b)\otimes(c\oplus d)\oplus b\otimes d]\otimes f_{k-1}\oplus (a\otimes c)\otimes 2^{n-1}\oplus (b\otimes d) \end{align*} \]

此时,只需要计算 \((a\oplus b)\otimes(c\oplus d),b\otimes d,a\otimes c,(a\otimes c)\otimes 2^{n-1}\)\(4\) 项,复杂度优化到了 \(O(4^k)\)

代码如下:

const u32 map_size=256;
u32 mp[map_size][map_size];
inline u32 nim_mul(u32 x,u32 y)
{
    if(x<y) swap(x,y);
    if(!y) return 0u;
    if(y==1) return x;
    if(x<map_size&&mp[x][y]) return mp[x][y];
    ull t=2;
    u32 len=1;
    while(t*t<=x) t=t*t,len<<=1;
    u32 a=x>>len,b=x&(t-1),c=y>>len,d=y&(t-1);
    u32 c1=nim_mul(a,c);
    u32 c2=nim_mul(a^b,c^d);
    u32 c3=nim_mul(b,d);
    u32 c4=nim_mul(c1,t>>1);
    if(x<map_size) return mp[x][y]= ((c2^c3)*t) ^ c3 ^ c4;
    return ((c2^c3)*t) ^ c3 ^ c4;
}

上面的乘法相当于正常乘法时按位模拟,其实我们可以折半,用 \(F\) 最均匀的把 \(x\) 分成 \(x=x_0F+x_1\),同理有 \(y=y_0F+y_1\)

于是有

\[\begin{align*} x\otimes y&=(x_0F\oplus x_1)\otimes(y_0F\oplus y_1)\\ &=(x_0\otimes y_0\otimes (F+\dfrac F2))\oplus[(x_0\otimes y_1\oplus x_1\otimes y_0)\otimes F]\oplus (x_1\otimes y_1)\\ &=[((x_0\oplus x_1)\otimes(y_0\oplus y_1)\oplus (x_1\otimes y_1))\otimes F]\oplus (x_0\otimes y_0)\otimes \dfrac F2\oplus (x_1\otimes y_1) \end{align*} \]

过程和之前差不多,不过因为是折半,所以迭代只进行 \(O(\log k)\) 轮,复杂度进一步优化到 \(O(4^{O(\log k)})\),或者说 \(O(\log (V)^{\log_2^3})\)

还可以用原根进一步优化,打表发现,\(k=4\) 时最小的原根是 \(258\),于是可以把 \(\otimes\) 转化为原根指数上的加法(这里是正常的自然数 \(+\))。

Nim 逆元,开方

我们考虑的 Nimber 在 \([0,f_k)\cap\mathbb{N}\) 中,这是一个有限域,考虑 \(f(x,y)=y\otimes x\),迭代这个函数 \(g(0)=f(x,x),g(k)=f(x,g(k-1))\)\(g\) 一定是有周期的,用一点群论(数论?)知识,这个周期的倍数中一定有 \(f_k\)

又因为 \(x\otimes 1=x,x^{f_k}=x\),所以 \(x^{f_k-1}=1\),这就是 Nim 求逆。

类似可以定义 \(\sqrt x=x^{2^{2^k-1}}\)

Nim 多项式

我们当然不可能在指数上用 Nim 数,于是我们在系数上使用 Nim 数。实际上,Nimber 是一个性质很好的数域,我们可以把之前学过的几乎任何东西搬上去。(比如高斯消元,多项式的一堆板子)

给出 Nim 多项式更严格的定义,我们称 \([0,2^{2^k})\cap \mathbb{Z}\) 内的 Nimber 构成的数域为 \(\mathbb{M}\)

一个 Nim 多项式就是 \(A(x)=a_0\oplus a_1\otimes x\oplus a_2\otimes x^2\oplus\ldots\)。简记为 \(A(x)=\mathbb{M}(a_0,a_1,a_2\ldots)\)

Nim 多项式的加就是 \(\oplus\),即 \(A(x)\oplus B(x)=A(x)+B(x)=A(x)-B(x)=(a_0\oplus b_0,a_1\oplus b_1,a_2\oplus b_2\ldots)\)

Nim 多项式的卷积:

\[\begin{align*} A(x)\otimes B(x)=(a_0\otimes b_0,a_1\otimes b_0\oplus a_0\otimes b_1,a_0\otimes b_2\oplus a_1\otimes b_1\oplus a_2\otimes b_0\ldots) \end{align*} \]

Nim 多项式的数乘和一般多项式不同,\(\lambda\times A(x)=(\lambda\times a_0,\lambda\times a_1\ldots)\),我们定义 \(0\times a=0,1\times a=a,\lambda\times a=((\lambda-1)\times a)\oplus a\)

但是我们发现我们无法定义 EGF,\(\dfrac x2\)\(\mathbb{M}\) 中不一定是一个存在的数。即使考虑 \(e^{A(x)}=B(x)\iff B^\prime(x)=B(x)\otimes A^\prime(x)\) 也不行,因为我们只能微分,不能积分。

一个合理的转化是给前 \(n\) 项的系数乘上 \(n!\),这样一定是整数,也一定在 \(\mathbb{M}\) 上存在,而我们常常只关心前若干项。接着,我们把 EGF 的卷积看做二项卷积,则有

\[A(x)\otimes B(x)=C(x)\\ c_{n+1}=\bigoplus\binom{n}{i}\times(a_{i+1}\otimes b_{n-i}) \]

\(\binom ni\) 当然是一个整数,由数乘的定义,我们只关心其奇偶。由 Lucas 定理,我们可以知道 \(\binom{a+b}{a}\equiv 1\pmod 2\) 当且仅当 \(a\operatorname{bitand}b=0\)。就有 \(\binom{n}{i}\times(a_{i+1}\otimes b_{n-i})=[i\operatorname{bitand}(n-i)=0](a_{i+1}\otimes b_{n-i})\),发现这就是子集卷积。

结合半在线卷积的知识,就可以 \(O(n^{\log_23})\) 算 Nim 多项式 \(\exp\) 了。(当然,默认预处理原根后可以 \(O(1)\) 计算 \(a\otimes b\)

这样的复杂度不够 poly,我们期望更快的速度。

我们用 \(A(x)\circ B(x)\) 表示 \(A(x)\)\(B(x)\) 的二项卷积。

普通卷积下,Nim 多项式不能 \(\exp\),但是二项卷积打破了这一屏障,我们期望二项卷积也可以实现求逆,这样我们就可以用 \(\ln\) 和牛顿迭代解决 \(\exp\) 了。

利用 Nim 数的性质 \(x\oplus x=0\),即自己的加法逆元是自己,我们可以得到 \(A(x)\circ A(x)=a_0\otimes a_0\)。这是非常优美的性质,如果有 \(a_0=1\),那么 \(A(x)\circ A(x)=1\),这意味着 \(A(x)\) 的二项卷积逆就是 \(A(x)\)

在二项卷积的意义下,\(e^{A(x)}=B(x)\iff B^\prime(x)=B(x)\circ A^\prime(x)\),沿用多项式 \(\exp\) 中的假设,我们有 \(a_0=0,b_0=1\)。于是就有 \(B(x)\circ B(x)=1\),那么

\[A^\prime(x)=B(x)\circ B^\prime(x) \]

我们虽然没有求出 \(\exp\),但我们可以 \(O(n\log^2n)\)\(\ln\) 了,于是还是可以牛迭。

多项式牛顿迭代直接改改就好了:

\[B(x)\equiv B_0(x)\circ(1\oplus\ln B_0(x)\oplus A(x))\pmod {x^n} \]

就得到了 \(O(n\log^2n)\) 的优秀做法。

习题

POJ 3533 Light Switching Game

图论带师?我也不知道叫什么名字

CF 1310F Bad Cryptography

划愤

posted @ 2022-11-03 16:18  嘉年华_efX  阅读(359)  评论(0编辑  收藏  举报