尼姆博弈
一篇很好的博客:戳我戳我
尼姆博弈的定义:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
更严谨的推导是:1.无法进行任何移动的局面先手必败。2.可以移动到必败态的局面,先手必胜;3.所有移动都导致后手必胜的局面是先手必败。
结论:对于一个Nim游戏的局面(a1,a2,...,an),它是先手必败态当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。
Nim游戏的形象具体论述:
as + bs + … + ms 是偶数
a1 + b1 + … + m1 是偶数
a0 + b0 + … + m0是偶数
23 = 8 | 22 = 4 | 21 = 2 | 20 = 1 | |
大小为7的堆
|
0
|
1
|
1
|
1
|
大小为9的堆
|
1
|
0
|
0
|
1
|
大小为12的堆
|
1
|
1
|
0
|
0
|
大小为15的堆
|
1
|
1
|
1
|
1
|
23 = 8 | 22 = 4 | 21 = 2 | 20 = 1 | |
大小为7的堆
|
0
|
1
|
1
|
1
|
大小为9的堆
|
1
|
0
|
0
|
1
|
大小为12的堆
|
0
|
0
|
0
|
1
|
大小为15的堆
|
1
|
1
|
1
|
1
|
归根结底,Nim取子游戏的关键在于游戏开始时游戏处于何种状态(平衡或非平衡)和第一个游戏人是否能够按照取子游戏的获胜策略来进行游戏。
Being a Good Boy in Spring Festival
现在我们不想研究到底先手为胜还是为负,我只想问大家:
——“先手的人如果想赢,第一步有几种选择呢?”
Input输入数据包含多个测试用例,每个测试用例占2行,首先一行包含一个整数M(1<M<=100),表示扑克牌的堆数,紧接着一行包含M个整数Ni(1<=Ni<=1000000,i=1…M),分别表示M堆扑克的数量。M为0则表示输入数据的结束。
Output如果先手的人能赢,请输出他第一步可行的方案数,否则请输出0,每个实例的输出占一行。
Sample Input
3
5 7 9
0
Sample Output
1
题解:
这里是对n堆的牌数去异或,如果值为0则表示必败。题目问我们第一布有哪几种方法胜利。
即就是第一步能够给对手构建多少个必败点。
由于一次只能对一堆排进行操作,假设我 操作第i堆牌(a张),抽出x张。
那么其余n-1堆牌的异或值是固定为b.
那么 (a - x)^ b == 0 时,对手必败。
到此可能有人像我一样觉得必须历遍所有a求出那个值x满足条件。其实不必要
由上式可知x只有唯一取值
而且 一个数 与 b 异或等于 0 即表明 这个数等于b.
所以反过来我们可以求出b, 令 a - x = b ;
只要b满足 b < a; 即能构造出x使得 (a - x)^ b == 0 时,对手必败!
1 #include<iostream>
2 #include<cstdio>
3 #include<cstring>
4 using namespace std;
5 int a[110];
6 int count;
7 int n;
8 int main()
9 {
10 while(~scanf("%d",&n)&&n)
11 {
12 int t=0;
13 for(int i=0;i<n;i++)
14 {
15 scanf("%d",&a[i]);
16 t^=a[i];
17 }
18 count=0;
19 if(t)
20 {
21
22 for(int i=0;i<n;i++)
23 {
24 int k=0;
25 for(int j=0;j<n;j++)
26 {
27 if(i!=j)
28 {
29 k^=a[j];
30 }
31 }
32 if(a[i]>=k)
33 count++;
34 }
35 }
36 printf("%d\n",count);
37 }
38 }