以下为百度百科的介绍(我把他们概括了一下):
1) 巴什博奕(Bash Game):
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个.最后取光者得胜.
2) 威佐夫博奕(Wythoff Game):
有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜.
3) 尼姆博奕(Nimm Game):
有三堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜.
4) Nim Staircase博奕:
这个问题是尼姆博弈的拓展:游戏开始时有许多硬币任意分布在楼梯上,共n阶楼梯从地面由下向上编号为0到n。游戏者在每次操作时可以将楼梯j(1<=j<=n)上的任意多但至少一个硬币移动到楼梯j-1上。游戏者轮流操作,将最后一枚硬币移至地上(0号)的人获胜。
下面来说怎么做:
1)Bash
首先是一个逆向的推理,n处胜,(n-1)、(n-2)...(n-m)处败,(n-m-1)处胜利...,于是问题转化为何时得到n、(n-m-1)...这些必胜点,只需每次走到这个点,让对方改变这个状态,则必胜。我们发现,每两个必胜点相差(m+1),所以若n mod (m+1)=0,则先手需要改变这个必胜状态,所以先手必败;反之若n mod (m+1)≠0,则先手可以立刻走到必胜点,所以先手必胜。
2)Wythoff
3)Nimm
这个推导比较麻烦,那就说一下结论吧。
定义sum为所有物品数量的异或,若sum==0则称此状态为奇异状态,记T,反之为非奇异状态,记S。若存在一个物品数量大于1,记2,反之所有物品数量为1,记0。
先手必胜条件:S2,T0.
反之为先手必败。
而且如果改变题意为取最后一个为输,只需反一下最后结果就可以了。
#include <cstdio> int n,sum,temp; bool bo; int main() { while(scanf("%d",&n)!=EOF) { sum=0; bo=false; for(int i=1;i<=n;++i) { scanf("%d",&temp); sum^=temp; if(temp>1) bo=true; } if((sum==0&&bo==false)||(sum!=0&&bo==true)) printf("Yes\n"); else printf("No\n"); } return 0; }
4)Nim Staircase
这个博弈很神奇,可以通过一些转化,使其变成Nimm博弈。下面介绍这种转化。
先考虑偶数的台阶,当A移动一定的硬币到下一个奇数台阶,B可以以移动同样数量的硬币到再下一个偶数台阶。此时,奇数台阶的硬币是没有改变的。若A一直移动偶数台阶的硬币,B也可以一直移动,直到移到0,B必定取最后一个硬币。
于是发现,偶数台阶不影响胜负,那么下面考虑奇数台阶。由于偶数不影响胜负,那么我们把所有偶数台阶看作地面,奇数台阶的硬币移动到下一层可以看作移到地面。也就是取走石子类似,那么问题就转化为了一个经典的Nimm博弈。问题就可以迎刃而解了。
介绍完以上的博弈基础,我们还会遇到一种组合博弈,这是需要一个很强大的工具:SG函数。
先定义几样东西:
1) mex(minimal excludant):表示最小的不属于这个集合的非负整数
2) f(x):对于顶点x, 玩家能够移动到x的点集记为f(x)
3) sg(x) = mex{sg(y) | y∈f(x)}
对于sg函数的计算,我们可以从前往后一次得到。
定理1:sg的值不会超过mex的集合中数量的最大值。
对于一个博弈论游戏,我们可以把他分解为几个单独的博弈论基础问题,求出sg函数值,最后对不同基础问题的所有sg函数值求异或,即可得到整个游戏的sg函数值。
hdu1848是一道模板题。
#include <cstdio> #include <cstring> #define FOR(i,x,y) for(int i=x;i<=y;++i) #define rFOR(i,x,y) for(int i=x;i>=y;--i) using namespace std; int n,m,p,sum,sg[1010],f[20]={1,1}; bool vis[20]; int mex(int a){ int temp; memset(vis,0,sizeof(vis));//初始化 rFOR(i,15,1)//逆着推 { temp=a-f[i]; if(temp>=0) vis[sg[temp]]=true;//删去所有可以走到的点 } FOR(i,0,16) if(!vis[i]) return i;//sg可能的最大值也就是f[i]的不同值的数量。 } int main(){ FOR(i,2,15) f[i]=f[i-1]+f[i-2];//能够移动到的点集 FOR(i,1,1000) sg[i]=mex(i);//sg函数计算 while(scanf("%d%d%d",&n,&m,&p)) { if(n==0&&m==0&&p==0) break; sum=0; sum=sg[n]^sg[m]^sg[p]; if(sum==0) printf("Nacci\n"); else printf("Fibo\n"); } return 0; }