ICG满足以下特征:
(1)有两个玩家,游戏规则对两个玩家公平
(2)游戏状态有限,能走的步数也是有限的
(3)轮流走,当一个玩家不能走时游戏结束
(4)游戏的局势不能区分玩家的身份,例如黑白棋就是不行的
特征:
给定初始局势,指定先手玩家,如果双方都采取最优策略,那么获胜者已经确定了,也就是说ICG问题存在必胜策略
巴什游戏:
有n个石子,甲先取,乙后取,每次可以拿出1~m颗石子,轮流拿下去,拿到最后一颗的人获胜
如果:n%(m+1)==0 先手败,否则先手胜
P-position:前一个玩家刚走一步的必胜位置
N-position:下一个玩家的必胜位置
尼姆游戏:
从一堆石头扩展到多维石头
对于任意的a1,a2....an,一个判断胜负的方法:做异或运算 nim-sum运算
如果a1^a2...^an!=0 先手必胜,否则先手必败
hdu 1850
下面是一个二人小游戏:桌子上有M堆扑克牌;每堆牌的数量分别为Ni(i=1…M);两人轮流进行;每走一步可以任意选择一堆并取走其中的任意张牌;桌子上的扑克全部取光,则游戏结束;最后一次取牌的人为胜者。
现在我们不想研究到底先手为胜还是为负,我只想问大家:
——“先手的人如果想赢,第一步有几种选择呢?”
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int sum=0,ans=0; int n; int a[110]; int main(){ while(scanf("%d",&n)!=EOF){ if(n==0) break; for(int i=1;i<=n;i++) scanf("%d",&a[i]); sum=0;ans=0; for(int i=1;i<=n;i++){ sum^=a[i]; } if(sum==0) printf("0 "); else{ for(int i=1;i<=n;i++){ if((sum^a[i])<=a[i]) ans++; } printf("%d ",ans); } } return 0; }
图游戏和Sprague-Grundy函数
Sprague-Grundy函数是巴什游戏、尼姆游戏的通用方法,用图作为分析工具
给定一个有向无环图,在一个起点放一个棋子,两个玩家交替将这个棋子沿着有向边移动,不能移动的就输了
把每个局势看作为一个点,每个局势和它的后继局势之间有一条有向边,抽象为了图游戏
!找到关键点,先手毕胜点、先手必败点
Sprague-Grundy函数
定义:在一个图G(X,F)中,把节点x的Sprague-Grundy函数定义为sg(x),等于没有指定给它任意后继节点的sg值的最小非负整数
求每个点的sg值:与用dp求p-positive和n-positive差不多,复杂度O(nm)
sg(x)=0的节点x是必败点,即p-positive点
例如:求解巴什游戏
hdu 1864
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //用sg函数求解 int c,n,m; int a[maxn]; int sg[maxn]; void getsg(){ memset(sg,0,sizeof(sg)); for(int i=1;i<=n;i++){ memset(a,0,sizeof(a)); for(int j=1;j<=m&&i-j>=0;j++) a[sg[i-j]]=1; //把i的后继节点放到集合a中 for(int j=0;j<=n;j++) if(!a[j]){//计算sg函数 sg[i]=j;break; } } } int main(){ cin>>c; while(c--){ cin>>n>>m; // if(n%(m+1)==0) printf("second "); // else printf("first "); getsg(); if(sg[n]) printf("first "); else printf("second "); } return 0; }
求解尼姆游戏
hdu 1848
任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的:
F(1)=1;
F(2)=2;
F(n)=F(n-1)+F(n-2)(n>=3);
所以,1,2,3,5,8,13……就是菲波那契数列。
在HDOJ上有不少相关的题目,比如1005 Fibonacci again就是曾经的浙江省赛题。
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;
假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int sg[maxn],s[maxn]; int fibo[]={1,2,3,5,8,13,21,34,55,89,144,233,377,610,987}; void getsg(){ for(int i=0;i<maxn;i++){ sg[i]=i; memset(s,0,sizeof(s)); for(int j=0;j<15&&i>=fibo[j];j++) s[sg[i-fibo[j]]]=1; for(int j=0;j<=i;j++){ if(!s[j]) { sg[i]=j;break; } } } } int main(){ getsg(); int n,m,p; while(cin>>n>>m>>p&&n+m+p){ if(sg[n]^sg[m]^sg[p]) cout<<"Fibo"<<endl; //先手赢 else cout<<"Nacci"<<endl; } return 0; }
威佐夫游戏
有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。
设两堆石子的数量为(a,b),先手必输的局势有(0,0),(1,2),(3,5),(4,7),(6,10),(8,13),(9,15)这些局势称为奇异局势
奇异局势特征:
(1)差值递增:1,2,3,4...
(2)每个局势的第一个数是前面没有出现过的最小的自然数
每个奇异局势的第一个数是这个局势的差值*黄金分割比例1.618然后取整
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; int main(){ int n,m; double gold=(1+sqrt(5.0))/2; while(cin>>n>>m){ int a=min(n,m); int b=max(n,m); double k=(double)(b-a); int test=(int)(k*gold); //差值*黄金分割数==?==第一个数 奇异局势是先手必输的局势 if(test==a) cout<<"0"<<endl; else cout<<"1"<<endl; } return 0; }