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\n");
        else{
            for(int i=1;i<=n;i++){
                if((sum^a[i])<=a[i]) ans++;
            }
            printf("%d\n",ans);
        }
        
    }
return 0;
}
View Code

 

图游戏和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\n");
    //    else printf("first\n");
    getsg();
    if(sg[n]) printf("first\n");
    else printf("second\n"); 
    }
return 0;
}
View Code

求解尼姆游戏

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;
}
View Code

 

威佐夫游戏

有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。

设两堆石子的数量为(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;
}
View Code

 

 posted on 2020-06-03 16:31  shirlybabyyy  阅读(515)  评论(0编辑  收藏  举报