Nim博弈-属于组合游戏
条件:
1、有两名选手;
2、两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;
3、对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、骰子的点数或者其它什么因素;
4、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。根据这个定义,很多日常的游戏并非ICG。例如象棋就不满足条件3,因为红方只能移动红子,黑方只能移动黑子,合法的移动集合取决于轮到哪名选手操作。
定义:
通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
这游戏看上去有点复杂,先从简单情况开始研究吧。如果轮到你的时候,只剩下一堆石子,那么此时的必胜策略肯定是把这堆石子全部拿完一颗也不给对手剩,然后对手就输了。如果剩下两堆不相等的石子,必胜策略是通过取多的一堆的石子将两堆石子变得相等,以后如果对手在某一堆里拿若干颗,你就可以在另一堆中拿同样多的颗数,直至胜利。如果你面对的是两堆相等的石子,那么此时你是没有任何必胜策略的,反而对手可以遵循上面的策略保证必胜。如果是三堆石子……好像已经很难分析了,看来我们必须要借助一些其它好用的(最好是程式化的)分析方法了,或者说,我们最好能够设计出一种在有必胜策略时就能找到必胜策略的算法。
定义P-position和N-position,其中P代表Previous,N代表Next。直观的说,上一次move的人有必胜策略的局面是P-position,也就是“后手可保证必胜”或者“先手必败”,现在轮到move的人有必胜策略的局面是N-position,也就是“先手可保证必胜”。更严谨的定义是:1.无法进行任何移动的局面(也就是terminal position)是P-position;2.可以移动到P-position的局面是N-position;3.所有移动都导致N-position的局面是P-position。
按照这个定义,如果局面不可能重现,或者说positions的集合可以进行拓扑排序,那么每个position或者是P-position或者是N-position,而且可以通过定义计算出来。
结论:
(Bouton‘s Theorem)对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。
证明:
根据定义,证明一种判断position的性质的方法的正确性,只需证明三个命题:
1、这个判断将所有terminal position判为P-position;
2、根据这个判断被判为N-position的局面一定可以移动到某个P-position;
3、根据这个判断被判为P-position的局面无法移动到某个P-position。
第一个命题显然,terminal position只有一个,就是全0,异或仍然是0。
第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某个合法的移动,将ai改变成ai‘后满足a1^a2^...^ai‘^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai‘=ai^k,此时a1^a2^...^ai‘^...^an=a1^a2^...^an^k=0。
第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai‘后满足a1^a2^...^ai‘^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai‘^...^an可以得到ai=ai‘。所以将ai改变成ai‘不是一个合法的移动。证毕。
根据这个定理,我们可以在O(n)的时间内判断一个Nim的局面的性质,且如果它是N-position,也可以在O(n)的时间内找到所有的必胜策略。
题目:
1.
away, cannot be zero and cannot be larger than the number of matches in the chosen pile). If after a player’s turn, there is no match left, the player is the winner. Suppose that the two players are all very clear. Your job is to tell whether the player who
plays first can win the game or not.
the number of matches in each pile.
2 45 45 3 3 6 9
No Yes
基础的Nim博弈问题:
(1)如果轮到某人取火柴的时候,火柴已经没有了,那么此人输,设为P-格局
(2)如果轮到某人取火柴的时候,他能够取完火柴,那么此人赢,设为N-格局
(3)如果轮到某人取火柴的时候,他能够将当前格局转化为某个P格局,那么此格局为N-格局
(4)如果轮到某人取火柴的时候,他不能将当前格局转化为某个P格局,那么此格局为P-格局
下面我们来推导一个定理:一个格局记为(x1,x2,...,xn),且次序无关,此格局为 P-格局 当且仅当 x1^x2^...^xn = 0.其中^是按位异或运算
推导: 对应上述4种情形(为了叙述的简洁,并不十分严谨地证明,而是直接假设结论正确,再说明定理的工作机制)
(1)当(x1,x2,...,xn)中全为0时 ,格局为P-格局,此时x1 ^ x2 ^ ... ^ xn = 0成立。
(2)当(x1,x2,...,xn)中只有一个不为0,格局为N-格局,此时x1 ^ x2 ^ ... ^ xn = 0不成立。
(3)当(x1,x2,...,xn)是P-格局时,x1,...,xn不全为0.(反证法)
假设x1 ^ x2 ^ ... ^ xn = p,且p不为0,
记 p 的二进制表示中,最左的1(最高位的1)在从左至右数第 k 位.
由于p是异或运算的结果,那么 x1, x2 , ... , xn中至少有一个数第k位为1,
不妨设 xi 的第 k 位为1,那么 xi ^ p 第 k 位为0,那么xi > xi^p 显然成立.
也就是说,存在某种取法,使i堆的火柴数变化到 xi^p .
题设x1 ^ x2 ^ ... ^ xn = p,那么x1 ^ x2 ^ ... ^ xn ^ p = 0.
那么当前格局可以转化到某个P-格局,也就是说当前格局时N-格局,矛盾
所以,必有p=0.
(4)当x1 ^ x2 ^ ... ^ xn = 0时,如果存在某个移动 xi 变化到 xi ’ ,且x1^x2^....^xi ‘ ^...^xn = 0,
那么由异或运算的消去律有,xi = xi ‘ ,也就是说一根火柴都没取,这不允许的,所以
当前格局只能是P格局
nyoj585-取石子-相同的题目:
#include <iostream> #include <stdio.h> using namespace std; int pile[1010]; int main(){ int nc; //cin>>nc; scanf("%d",&nc); while(nc--){ int M;scanf("%d",&M); int result = 0; for(int i=0; i<M; ++i) { scanf("%d",&pile[i]); // cin>>pile[i]; result ^= pile[i]; } if(result) puts("PIAOYI\n"); //cout<<"PIAOYI"<<endl; else puts("HRDV\n"); // cout<<"HRDV" <<endl; } return 0; }
变形:nyoj888
取石子(九)
时间限制:1000 ms | 内存限制:65535 KB
难度:4
- 描述
-
最近TopCoder的Yougth和Hrdv在玩一个游戏,游戏是这样的。
有n堆石子,两个人轮流从其中某一堆中任意取走一定的石子,最后不能取的为赢家,注意: 每次只能从一堆取任意个,可以取完这堆,但不能不取。
假设Yougth先取,输入赢了的人名字、
- 输入
- 第一行输入n,代表有n组测试数据(n<=10000)
以下每组测试数据包含两行:第一行:包含一个整数m,代表本组测试数据有m(m<=1000)堆石子;
:第二行:包含m个整数Ai(Ai<=10000),分别代表第i堆石子的数量。
- 输出
- 若Yougth赢输出“Yougth”,否则输出“Hrdv”注意每组结果占一行。。
- 样例输入
-
3 2 1 1 3 3 8 11 2 5 10
- 样例输出
-
Yougth Hrdv Yougth
anti-nim游戏
这题与以往的博弈题的胜负条件不同,谁走完最后一步谁输
首先给出结论: 先手胜当且仅当
(1)所有堆石子数都为1且游戏的result值为0 ,(2)存在某堆石子数大于1且游戏的result值不为0
证明:
1。显然成立。
2。分两种情况
a.只有一堆石子>1。你可以把它变成1。
b.至少两队石子>1,你只需要把SG值边为0就可以了,这个操作之后,至少还有两堆石子>1,然后对方随便怎么操作,都会把SG变成非0
还需要证明1,2的反面是必败的。
1显然。
2你会把SG变成非0,而且因为现在至少两堆石子>1了,所以你还会给人家至少留一个>1的,那么无论怎么搞,都会送给后手一个必胜态。
代码:
#include<stdio.h> int main() { int T,n,a,i,result,count; scanf("%d",&T); while(T--) { scanf("%d",&n); result=0; count=0; for(i=1; i<=n; i++) { scanf("%d",&a); result=result^a; if(a>1) count++; } if((count&&result)||(!count&&!result)) printf("Yougth\n"); else printf("Hrdv\n"); } return 0; }
2.输出可以赢的第一步走的方法数
桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
——“先手的人如果想赢,第一步有几种选择呢?”
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
输出先手能赢的方法数
我们知道在Nim博弈中
如果我们面对的是一个非奇异局势(a,b,c),要如何变为奇异局势呢?假设 a < b< c,我们只要将 c 变为 a^b,即可,因为有如下的运算结果: a^b^(a^b)=(a^a)^(b^b)=0^0=0。要将c 变为a^b,只要从 c中减去 c-(a^b)即可。
也就是说,其中一个堆C必须大于所有其他所有堆异或的值,由此我们可以进行枚举
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int main() { int n,a[105],i,j,cnt; while(~scanf("%d",&n),n) { cnt = 0; for(i = 0; i<n; i++) scanf("%d",&a[i]); for(i = 0; i<n; i++) { int s = 0; for(j = 0; j<n; j++) { if(i!=j) s^=a[j]; } if(a[i] > s) cnt++; } printf("%d\n",cnt); } return 0; }
3.输出上述的第一步走法:
Problem Description
m堆石子,两人轮流取.只能在1堆中取.取完者胜.先取者负输出No.先取者胜输出Yes,然后输出怎样取子.例如5堆 5,7,8,9,10先取者胜,先取者第1次取时可以从有8个的那一堆取走7个剩下1个,也可以从有9个的中那一堆取走9个剩下0个,也可以从有10个的中那一堆取走7个剩下3个.
Input
输入有多组.每组第1行是m,m<=200000. 后面m个非零正整数.m=0退出.
Output
先取者负输出No.先取者胜输出Yes,然后输出先取者第1次取子的所有方法.如果从有a个石子的堆中取若干个后剩下b个后会胜就输出a b.参看Sample Output.
Sample Input
2 45 45 3 3 6 9 5 5 7 8 9 10 0
Sample Output
No Yes 9 5 Yes 8 1 9 0 10 3
求出在第i堆取完石子后应该剩下多少个与其它堆相异或等于0,这是留给对手的是必败态,所以只要求出其它堆的异或值就好了,因为两个相同的数异或以后才是0。
代码:
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int a[200005],ans[200005][2]; int main() { int n,i,j,cnt,s; while(~scanf("%d",&n),n) { cnt = 0; s = 0; for(i = 0; i<n; i++) { scanf("%d",&a[i]); s^=a[i]; } for(i = 0; i<n; i++) { if(a[i] > (s^a[i])) { ans[cnt][0] = a[i]; ans[cnt][1] = s^a[i]; cnt++; } } if(cnt) { printf("Yes\n"); for(i = 0; i<cnt; i++) printf("%d %d\n",ans[i][0],ans[i][1]); } else printf("No\n"); } return 0; }
题目描述: 两个人玩游戏,在标有1,2,3,4,5...的格子上有一些棋子,规则是选一枚棋子移动,要求不能跨越棋子移动,必需向左移动(可以移动任意格),不能移动的就输掉比赛。 下面是一个棋局的例子: +--+--+--+--+--+--+--+--+--+ |1x|2 |3x|4 |5 |6x|7x|8 |9 |(旁边标有x的表示在这里有棋子) +--+--+--+--+--+--+--+--+--+ 给出棋子的初始位置,若先手胜,输出"Georgia will win",否则输出"Bob will win"(不含引号)。 输入格式: 多组数据。 对于每组数据,第一行是有一个T,表示有T组数据。 对于每组数据,第一行有一个N,表示有N枚棋子。 接下来的一行有N个数,分别为每个棋子的位置。 输出格式: 对每组数据,输出一行,如题目描述那样。 样例输入: 2 3 1 2 3 8 1 5 6 7 9 12 14 17 样例输出: Bob will win Georgia will win
核心思想在于想熟知的Nim游戏进行转化,可以把空格数作为石子数,每一步既可以从任一堆石子中取若干,也可以将最右侧的石子扔掉。而且经分析可知,偶数位置的石子实际上不发挥任何作用。
#include<iostream> #include<algorithm> using namespace std; int main(){ int t,n,sum,position[10005]; cin>>t; while(t--){ cin>>n; position[0]=0; for(int i=1;i<=n;i++) cin>>position[i]; sort(position,position+n+1); sum=0; for(int i=n;i>0;i-=2){ sum=sum^(position[i]-position[i-1]-1); } if(sum==0) cout<<"Bob will win"<<endl; else cout<<"Georgia will win"<<endl; } //system("pause"); return 0; }
博弈-Nim博弈