Nim 游戏及其变形
Posted clover_hxy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nim 游戏及其变形相关的知识,希望对你有一定的参考价值。
Nim 在博弈中经常出现,很多看似复杂的题目,在分析和变形之后就回归了最初的nim游戏。
经典的nim游戏
一共有N堆石子,编号1..n,第i堆中有个a[i]个石子。
每一次操作Alice和Bob可以从任意一堆石子中取出任意数量的石子,至少取一颗,至多取出这一堆剩下的所有石子。
两个人轮流行动,取走最后一个的人胜利。Alice为先手。
我们定义Position
P:表示当前局面下先手必败
N:表示当前局面下先手必胜
N,P状态的转移满足如下性质:
1.合法操作集合为空的局面为P
2.可以移动到P的局面为N,这个很好理解,以为只要能转换到P局面,那么先手只需要使操作后变成P局面,那么后手就面临了一个必败的状态。
3.所有移动只能到达N的局面为P。无论怎么选取都会留给对手一个必胜状态。
其实知道这个之后应该是可以记忆化搜索或者用sg函数求解的,但是如果范围非常大,就没法做了。
就引进了nim游戏一个很神奇的结论:对于一个局面,当且仅当a[1] xor a[2] xor ...xor a[n]=0时,该局面为P局面,即必败局面。
证明如下:
1.全0的局面一定是P局面。
2.从任意一个异或值不为0(设为K)的局面一定可以转移到一个异或值为0的状态。由于异或计算的特殊性,我们知道一定有一个a[i]的某一位与k的最高位的1是相同的,那么必然有a[i] xor k<a[i],我们可以通过改变a[i[的值为a[i]',使a[1] xor a[2] xor a[i] xor ...xor a[n]=0
3.对于任意一个局面,若异或值为0,则不存在任何一个移动可以使新的局面的异或值为0.如果一位的异或值为0,那么这一位上一定有偶数个1,那么只改变一个数,一定无法使其保持0.
Moore’s Nimk
n堆石子,每次从不超过k堆中取任意多个石子,最后不能取的人失败。
这是一个nim游戏的变形,也是有结论的。
结论为:把n堆石子的石子数用二进制表示,统计每个二进制位上1的个数,若每一位上1的个数mod(k+1)全部为0,则必败,否则必胜。
证明如下:
1.全为0的局面一定是必败态。
2.任何一个P状态,经过一次操作以后必然会到达N状态:在某一次移动中,至少有一堆被改变,也就是说至少有一个二进制位被改变。由于最多只能改变k堆石子,所以对于任何一个二进制位,1的个数至多改变k。而由于原先的总数为k+1的整数倍,所以改变之后必然不可能是k+1的整数倍。故在P状态下一次操作的结果必然是N状态。
3.任何N状态,总有一种操作使其变化成P状态。从高位到低位考虑所有的二进制位。假设用了某种方法,改变了m堆,使i为之前的所有位都回归到k+1的整数倍。现在要证明总有一种方法让第i位也恢复到k+1的整数倍。
有一个比较显然的性质,对于那些已经改变的m堆,当前位可以自由选择1或0.
设除去已经更改的m堆,剩下堆i位上1的总和为sum
分类讨论:
(1)sum<=k-m,此时可以将这些堆上的1全部拿掉,然后让那m堆得i位全部置成0.
(2)sum>k-m 此时我们在之前改变的m堆中选择k+1-sum堆,将他们的第i位设置成1。剩下的设置成0.由于k+1-sum<k+1-(k-m)<m+1,也就是说k+1-sum<=m,故这是可以达到的。
anti-nim
反nim游戏。正常的nim游戏是取走最后一颗的人获胜,而反nim游戏是取走最后一颗的人输。
一个状态为必胜态,当且仅当:
1)所有堆的石子个数为1,且NIM_sum=0
2)至少有一堆的石子个数大于1,且 NIM_sum≠0
题目:bzoj 1022: [SHOI2008]小约翰的游戏John
#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int main()
scanf("%d",&m);
for (int j=1;j<=m;j++)
scanf("%d",&n);
int ans=0; int pd=0;
for (int i=1;i<=n;i++)
int x; scanf("%d",&x);
if (x>1) pd=1;
ans^=x;
if (pd==0&&!ans) printf("John\\n");
else if (pd==1&&ans) printf("John\\n");
else printf("Brother\\n");
威佐夫博弈
两堆石子,每次可以取一堆或两堆,从两堆中取得时候个数必须相同,先取完的获胜。
这种情况下是颇为复杂的。我们用(ak,bk)(ak ≤ bk ,k=0,1,2,…,n)表示
两堆物品的数量并称其为局势,如果甲面对(0,0),那么甲已经输了,这种局势我们
称为奇异局势。前几个奇异局势是:(0,0)、(1,2)、(3,5)、(4,7)、(6,
10)、(8,13)、(9,15)、(11,18)、(12,20)。
可以看出,a0=b0=0,ak是未在前面出现过的最小自然数,而 bk= ak + k,奇异局势有
如下三条性质:
1。任何自然数都包含在一个且仅有一个奇异局势中。
由于ak是未在前面出现过的最小自然数,所以有ak > ak-1 ,而 bk= ak + k > ak
-1 + k-1 = bk-1 > ak-1 。所以性质1。成立。
2。任意操作都可将奇异局势变为非奇异局势。
事实上,若只改变奇异局势(ak,bk)的某一个分量,那么另一个分量不可能在其
他奇异局势中,所以必然是非奇异局势。如果使(ak,bk)的两个分量同时减少,则由
于其差不变,且不可能是其他奇异局势的差,因此也是非奇异局势。
3。采用适当的方法,可以将非奇异局势变为奇异局势。
假设面对的局势是(a,b),若 b = a,则同时从两堆中取走 a 个物体,就变为了
奇异局势(0,0);如果a = ak ,b > bk,那么,取走b – bk个物体,即变为奇异局
势;如果 a = ak , b < bk ,则同时从两堆中拿走 ak – ab – ak个物体,变为奇异局
势( ab – ak , ab – ak+ b – ak);如果a > ak ,b= ak + k,则从第一堆中拿走多余
的数量a – ak 即可;如果a < ak ,b= ak + k,分两种情况,第一种,a=aj (j < k)
,从第二堆里面拿走 b – bj 即可;第二种,a=bj (j < k),从第二堆里面拿走 b – a
j 即可。
从如上性质可知,两个人如果都采用正确操作,那么面对非奇异局势,先拿者必胜
;反之,则后拿者取胜。
那么任给一个局势(a,b),怎样判断它是不是奇异局势呢?我们有如下公式:
ak =[k(1+√5)/2],bk= ak + k (k=0,1,2,…,n 方括号表示取整函数)
题目:http://poj.org/problem?id=1067
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,m;
int main()
double k=(1+sqrt(5.0))/2;
while(scanf("%d%d",&n,&m)!=EOF)
if (n>m) swap(n,m);
int t=m-n;
if (n==(int)((double)t*k)) printf("0\\n");
else printf("1\\n");
巴什博奕
只有一堆石子共n个。每次从最少取1个,最多取m个,最后取光的人取胜。
问先手是否有必胜策略,第一步该怎么取。
如果n=(m+1)*k+s (s!=0) 那么先手一定必胜,因为第一次取走s个,接下来无论对手怎么取,我们都能保证取到所有(m+1)倍数的点,那么循环下去一定能取到最后一个。
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1846
#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int main()
int t;
scanf("%d",&t);
for (int i=1;i<=t;i++)
scanf("%d%d",&n,&m);
if (n%(m+1)) printf("first\\n");
else printf("second\\n");
n堆石子,每次可以取走一堆石子,然后放入两堆规模更小的石子(可以为0).最后不能操作的人输。
这种题貌似需要用SG函数来解。
f[i]表示还剩一堆i颗石子堆得状态,f[i][j]表示两堆的状态,然后依次类推。
根据SG函数的定义有f[i]=minn∈N|n!=f(p,q) i>p>=0&&i>q>=0
然后递推求得子游戏的任意状态。
staircase nim
顾名思义就是在阶梯上进行,每层有若干个石子,每次可以选择任意层的任意个石子将其移动到该层的下一层。最后不能操作的人输。 阶梯博弈经过转换可以变为Nim..把所有奇数阶梯看成N堆石子做nim。把石子从奇数堆移动到偶数堆可以理解为拿走石子,就相当于几个奇数堆的石子在做Nim。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 2003
using namespace std;
int m,n;
int a[N],p[N],b[N];
int main()
freopen("a.in","r",stdin);
scanf("%d",&m);
for (int i=1;i<=m;i++)
scanf("%d",&n); int ans=0;
int cnt=0;
for (int j=1;j<=n;j++) scanf("%d",&a[j]);
sort(a+1,a+n+1);
for (int j=2;j<=n;j++) p[j]=a[j]-a[j-1]-1;
p[1]=a[1]-1;
for (int j=1;j<=n;j++) b[j]=p[n-j+1];
for (int j=1;j<=n;j++)
if (!b[j]) cnt++;
if (j&1) ans^=b[j];
//cout<<b[j]<<" ";
//cout<<endl;
if (cnt==n)
printf("Bob will win\\n");
continue;
if (ans) printf("Georgia will win\\n");
else printf("Bob will win\\n");
以上是关于Nim 游戏及其变形的主要内容,如果未能解决你的问题,请参考以下文章
题解Luogu P2972 [USACO10HOL]岩石和树木Rocks and Trees