【博弈论】找出游戏的必胜策略 挑战书上的题目

 

涉及到的理论

思路:被分成两个相同的组的状态是必败态

作出对称的状态后再模仿对手

如果个数不大于2,Alice能一次拿完,否则Alice每拿一次,Bob能通过取走一枚或两枚硬币将其分成两个长度相同的链,则留给Alice的是必败态。

#include<iostream>
using namespace std;
int main()
{
    int n;
    while(cin>>n)
    {
        if(!n) break;
        if(n<=2) cout<<"Alice"<<endl;
        else cout<<"Bob"<<endl;
    }
    return 0;
}

https://www.luogu.org/blog/zyy/solution-p1290 这个题解很好的鸭

#include<iostream>
#include<algorithm>
using namespace std;
int a,b;
void solve()
{
    bool f=true;                    //f为先手是否必胜
    for(;;)
    {
        if(a>b) swap(a,b);
        if(b%a==0) break;           //出现了一个必胜态
        if(b>2*a) break;            //另一个必胜态
        b-=a;
        f=!f;
    }
    if(f)   cout<<"Stan wins"<<endl;
    else cout<<"Ollie wins"<<endl;
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;++i)
    {
        cin>>a>>b;
        solve();
    }
    return 0;
}

 

思路:将棋子两两配对,如果为奇数,就在p=0的位置添加一个棋子

将棋子之间的空格数看作一堆石子,根据Nim游戏的策略,如果石子数的异或值!=0,先手必胜

#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e3+10;
int p[N];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        for(int i=1;i<=n;++i)
            cin>>p[i];
        if(n%2) p[++n]=0;
        //cout<<n<<endl;
        sort(p+1,p+1+n);
        int x=0;
        for(int i=1;i+1<=n;i+=2)
            x^=(p[i+1]-p[i]-1);     //p[i]和p[i+1]之间有多少个格子
        if(x) cout<<"Georgia will win"<<endl;
        else cout<<"Bob will win"<<endl;
    }
    return 0;
}

 

poj 2960 S-Nim  SG函数

题意:每个测试用例第一行给出一个集合S,每次得取S里面的数个珠子,第二行给出游戏个数m,接下来m行给出分别给出每次游戏中堆的个数以及每堆有多少个珠子,如果先手胜输出W,先手败输出L。每行样例后输出一新行。

思路:和硬币游戏2基本一样,就是在Nim上加了一个条件,每次得选给定集合里的数个硬币

n堆石子的Nim游戏本身就是n个“任取石子游戏”的和,每个任取石子游戏的SG值为石子个数(考虑定义SG函数值是第一个转移不到的整数),所以Nim的话就是所有石子数异或

Nim中有x颗石子的石子堆,能转移成有0,1,...,x-1颗石子的石子堆

从Grundy值为x的状态出发,可以转移到Grundy值为0,1,...x-1的状态。

注意这道题如果每种position都算一次的话会T

对于给定的每次可取硬币个数的集合,Grundy值就是定值,因此可以提前打表算好,或者用记忆化搜索

下为打表版本

#include<iostream>
#include<set>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=10100;
int s[110];
int heap[110];
int grundy[10100];
bool vis[10100];
int maxx=0;
int k;
int n;
void pre()
{
     memset(grundy,-1,sizeof(grundy));   //对于每个s,grundy都是相同的,不需要重复计算
     grundy[0]=0;
     for(int i=1;i<N;++i)
     {
         memset(vis,0,sizeof(vis));
         for(int j=1;j<=k;++j)
         {
             if(i>=s[j])
             {
                 int tmp=i-s[j];
                 vis[grundy[tmp]]=1;
             }
         }
         for(int j=0;j<N;++j)
         {
             if(!vis[j])
             {
                 grundy[i]=j;
                 break;
             }
         }
     }

}
int main()
{
    while(cin>>k)
    {

        if(!k) break;
        for(int i=1;i<=k;++i)
            cin>>s[i];
        pre();
        int m;      //m种情况
        cin>>m;
        while(m--)
        {
            cin>>n;
            int x=0;
            for(int i=1;i<=n;++i)
            {
                cin>>heap[i];       //每堆有多少个珠子
                x^=grundy[heap[i]];
            }
            if(x) cout<<'W';
            else cout<<'L';
        }
        cout<<endl;
    }

}

记忆化搜索(T)用set会T啊啊啊

#include<iostream>
#include<set>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int s[110];
int heap[110];
int grundy[10100];
int maxx=0;
int k;
int n;
int solve(int x)                        //一个游戏的解  记忆化搜索
{
    if(grundy[x]!=-1) return grundy[x];
    set<int>ss;
    for(int i=1;i<=k;++i)
    {
            if(s[i]<=x) ss.insert(solve(x-s[i]));
    }
    int g=0;
    while(ss.count(g)!=0) g++;
    grundy[x]=g;
    return grundy[x];
}
int main()
{

    while(scanf("%d",&k)!=EOF)
    {
        if(!k) break;
        for(int i=1;i<=k;++i)
            scanf("%d",&s[i]);
        memset(grundy,-1,sizeof(grundy));       //给定一组s,grundy是确定的
        grundy[0]=0;
        int t;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d",&n);
            int x=0;
            for(int i=1;i<=n;++i)
            {
                scanf("%d",&heap[i]);
                x^=solve(heap[i]);
            }
            if(x) cout<<'W';
            else cout<<'L';
        }
        cout<<endl;
    }
}

然后就换种写法

我喵喵喵?气的昏古去,bool vis[]数组开在外面了。。。回退的时候修改了当前状态的vis数组

awsl

#include<iostream>
#include<set>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int s[110];
int heap[110];
int grundy[10100];

//int sg[10010];
int maxx=0;
int k;
int n;

int solve(int x)
{

    if(grundy[x]!=-1) return grundy[x];
    int vis[10100];                       //嗯嗯嗯?
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=k;++i)
    {
        if(s[i]<=x)
        {
            vis[solve(x-s[i])]=1;
        }
    }
    for(int i=0;i<10100;++i)
    {
        if(!vis[i])
            return grundy[x]=i;
    }


}

int main()
{
    while(cin>>k)
    {
        memset(grundy,-1,sizeof(grundy));   //对于每个s,grundy都是相同的,不需要重复计算
        grundy[0]=0;
        if(!k) break;
        for(int i=1;i<=k;++i)
            cin>>s[i];
        int m;      //m种情况
        cin>>m;
        while(m--)
        {
            cin>>n;
            int x=0;
            for(int i=1;i<=n;++i)
            {
                cin>>heap[i];       //每堆有多少个珠子
                x^=solve(heap[i]);
            }
            if(x) cout<<'W';
            else cout<<'L';
        }
        cout<<endl;
    }

}

思路:首先感受一下这个游戏符合ICG

1、有两名选手

2、两名选手交替对游戏进行移动(move),每次一步,选手可以在(一般而言)有限的合法移动集合中任选一种进行移动;

3、对于游戏的任何一种可能的局面,合法的移动集合只取决于这个局面本身,不取决于轮到哪名选手操作、以前的任何操作、其它什么因素;

4、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。

以下摘自《挑战》,会发生分割的游戏,也能计算Grundy值,当w*h的纸张分为两张时,假设所分得的纸张的Grundy值分别为g1,g2,则这两张纸对应的状态的Grundy值可以表示为g1^g2

枚举所有一步能转移到的状态的Grundy值,来计算Grundy值

记忆化搜索

#include<iostream>
#include<cstdio>
#include<set>
#include<cstring>
using namespace std;
const int N=210;
int mem[N][N];
int sg(int w,int h)
{
    if(mem[w][h]!=-1) return mem[w][h];
    set<int>s;
    for(int i=2;w-i>=2;++i) s.insert(sg(w-i,h)^sg(i,h));
    for(int i=2;h-i>=2;++i) s.insert(sg(w,h-i)^sg(w,i));
    int g=0;
    while(s.count(g)) g++;
    return mem[w][h]=g;
}
int main()
{
    int w,h;
    memset(mem,-1,sizeof(mem));
    while(scanf("%d%d",&w,&h)!=EOF)
    {
        if(sg(w,h)!=0) cout<<"WIN"<<endl;
        else cout<<"LOSE"<<endl;
    }
    return 0;
}

 

关于Grundy值的上限,只要考虑一张纸最多能转移的状态数,最多能转移200次

int sg(int w,int h)
{
    if(mem[w][h]!=-1) return mem[w][h];
    bool vis[210];                210就A了
    memset(vis,0,sizeof(vis));
    for(int i=2;w-i>=2;++i)
    {
        int tmp=sg(w-i,h)^sg(i,h);
        vis[tmp]=1;
    }
    for(int i=2;h-i>=2;++i)
    {
        int tmp=sg(w,h-i)^sg(w,i);
        vis[tmp]=1;
    }
    for(int i=0;;++i)
        if(!vis[i])
        return mem[w][h]=i;


    
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值