1. 程式人生 > 其它 >Nimk博弈原理與證明

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\) 則先手必敗

那麼我們進行類比並猜想:對於Nimk,計算二進位制表示下每一位上的 \(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\) 個數字進行改變

例題

POJ 2315

本題是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;
}