尼姆博弈(Nimm Game)
有3堆任意多的物品(x, y, z)。两个人轮流拿,每次只能从一堆中拿,至少拿一个,至多不限。拿到最后者胜利。
结论:
必败点为 x^y^z = 0。(^为异或)
证明:
如果当前局势为(0,0,0)。先手肯定输。其中0^0^0=0
如果当前局势为(0,n,n)。先手肯定输。因为无论先手怎么拿,后手都可以从另一堆里拿相同多个。最终转化成(0,0,0)的情况。其中0^n^n=0
如果当前局势为(1,2,3)。先手肯定输。无论先手怎么拿,后手都可以将局势转为(0,n,n)的情况。其中1^2^3=0
以(1,2,3)为例:
10进制 | 二进制 |
---|---|
1 | 01 |
2 | 10 |
3 | 11 |
当前为奇异局势当且仅当二进制的各位的和为偶数,称达到平衡态,达到平衡态的每一位称位平衡位。无论先手怎么拿,后手都可以通过一步操作将局势转为新的平衡态。所以x^y^z=0为奇异局势。先手必输。
将非奇异局势转化为奇异局势只需将z变成x^y即可。因为x^y^z = x^y^(x^y) = (x^x)^(y^y) = 0^0 = 0
任意堆的推广:
1堆的时候先手赢。x != 0
2堆的时候取决于两堆的数目是否相等,相等的话x^y=0,后手赢,不相等x^y!=0,先手赢
推广到任意多堆的情况(x1, x2, x3··· ···xn)
例如当n=4的时候,各堆大小分别是8,12,14,16
十进制 | 二进制 |
---|---|
8 | 01000 |
12 | 01100 |
14 | 01110 |
16 | 10000 |
先手可以从16的堆里拿走6个使之成为平衡态
十进制 | 二进制 |
---|---|
8 | 01000 |
12 | 01100 |
14 | 01110 |
10 | 01010 |
之后无论后手怎么拿,先手都可以使之重新成为平衡态。所以最后先手一定胜利。
递推关系的验证:
1.每一个n状态(必胜态)都可以通过一步转为p状态(必败态)
例如当前异或和X为1000101,总可以寻找到一个堆Y与X最高位的同位为1,例如Y=1001110,Z = X^Y=0001011,Z肯定小于Y,则可以将Y堆拿出(Y-Z)个物品,使得异或和为0达到平衡态。
设没操作之前的异或和为X,当前堆为Y,其他堆异或和为Z,即Z^Y=X,然后将当前堆改为Y^X。则操作之后的异或和等于Z^Y^X=X^X=0(p状态)
2.每个p状态不可能通过一步操作转为p状态
p状态异或和为0,如果只对一堆操作,无论拿多少个异或和都不可能为全0。
所以对于任意多堆时结论仍然成立。
例题:
HDU - 1850
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 100 + 5;
int a[maxn];
int n;
int main() {
while(cin >> n, n) {
int ans = 0;
for(int i = 0; i < n; i++) {
cin >> a[i];
ans ^= a[i];
}
int cnt = 0;
for(int i = 0; i < n; i++) {
if(a[i] > (a[i]^ans)) cnt++;
}
cout << cnt << endl;
}
return 0;
}