Nimk博弈原理与证明
简介
Nimk博弈是Nim博弈的变形,它的定义是:
给定 \(n\) 堆物品,第 \(i\) 堆物品有 \(A_i\) 个,两人轮流取,每次可以从不超过 \(k\) 堆的物品里取走任意多个物品,可以取光但不能不取,最后把物品全部取完者胜利
判断先手是否有必胜策略
推理
Nim博弈实际上就是 \(k=1\) 的Nimk博弈,考虑Nim博弈的结论:\(A_1\oplus A_2\oplus\cdots\oplus A_n=0\) 时先手必败,这等价于把所有物品数在某一个二进制位上 \(1\) 的个数相加再模 \(2\) ,若所有结果都为 \(0\) 则先手必败
那么我们进行类比并猜想:计算所有物品数在某一个二进制位上的 \(1\) 的个数再模 \(k+1\) ,若对任意一个二进制位结果都为 \(0\) ,那么先手必败
证明:
-
终止局面每堆物品个数都为 \(0\) ,为必败态
-
对于某个局面,如果存在某些二进制位上 \(1\) 的个数模 \(k+1\) 不为 \(0\) ,设满足 \(1\) 的个数模 \(k+1\) 不为 \(0\) 的最高位上有 \(m\) 个 \(1\) ,任取 \(t=m\bmod (k+1)\) 个 \(1\) ,把它们全都变为 \(0\) ,此时改变了 \(t\) 堆,若遇到下一个 \(1\) 的个数模 \(k+1\) 不为 \(0\) 的二进制位上有 \(r\) 个 \(1\) ,设原先改变的 \(t\) 堆在这一位上有 \(a\) 个 \(1\) 和 \(b\) 个 \(0\)
-
若 \(a\geq r\bmod (k+1)\) 那么就把 \(r\bmod (k+1)\) 个 \(1\) 变为 \(0\)
-
若 \(b\geq k+1-r\bmod (k+1)\) 则把 \(k+1-r\bmod (k+1)\) 个 \(0\) 变为 \(1\)
-
否则,选择原来改变的 \(t\) 堆之外的 \(r\bmod (k+1)-a\) 堆,将 \(1\) 变为 \(0\) ,再将 \(t\) 推中的 \(a\) 个 \(1\) 变为 \(0\) ,那么一共进行了:
\[a+b+r\bmod (k+1)-a=b+r\bmod (k+1)<k+1 \]次变化,符合要求
所以一定存在一种合法移动,使每个二进制位上 \(1\) 的个数模 \(k+1\) 都为 \(0\)
-
-
对于某个局面,如果每个二进制位上 \(1\) 的个数模 \(k+1\) 都为 \(0\) ,那一定不存在一种合法移动使移动后每个二进制位上 \(1\) 的个数模 \(k+1\) 仍为 \(0\) ,因为这至少要在一位上对 \(k+1\) 个数字进行改变,但最多对 \(k\) 个数字进行改变
例题
本题是Bash博弈和Nimk博弈的结合,先计算所有子游戏的SG函数值,这等价于Nimk博弈中的每堆物品的物品数,这样就可以用Nimk博弈的结论判断胜负了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const double PI = 3.1415926535;
int n, m, l, r, k, maxx;
int s[30 + 5], sg[30 + 5];
int main()
{
while(scanf("%d%d%d%d", &n, &m, &l, &r) != EOF) {
k = l / (2.0 * PI * r);
maxx = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &s[i]);
s[i] = ceil((double)s[i] / (2.0 * PI * r));
sg[i] = s[i] % (k + 1);
maxx = max(maxx, sg[i]);
}
int len = log2(maxx) + 1;
bool win = false;
for(int i = 1; i <= len; i++) {
int s = 0;
for(int j = 1; j <= n; j++) {
s += sg[j] % 2;
sg[j] /= 2;
}
if(s % (m + 1)) {
win = true;
break;
}
}
printf("%s\n", win ? "Alice" : "Bob");
}
return 0;
}