Game

题目描述

两个人玩一个游戏。有一个1*n的棋盘,有一些格子已经染了色。两个人轮流操作,每次一个人可以选择一个没有染色的格子,把它染成白色或黑色,要求相邻的格子不能染成相同的颜色。最后不能操作的那个人为输。
给出棋盘的初始状况,并且假设两个人都采取最优策略,问谁会赢?

SG分析

我们设:
sg[0,n]表示现在有连续n个未涂色格子,其两端的格子是不同颜色的估价函数值。
sg[1,n]表示现在有连续n个未涂色格子,其两端的格子是相同颜色的估价函数值。
sg[2,n]表示现在有连续n个未涂色格子,其有一端是格子另一端是边界。
sg[3,n]表示现在有连续n个未涂色格子,其两端的格子都是边界。
举个例子
10002
那么这归属状态sg[0,3]
10001
那么这归属状态sg[1,3]
0001
那么这归属状态sg[2,3]
000
那么这归属状态sg[3,3]
现在我们来推一推它们的转移,懒得写了详见代码
然后打个表,我们可以发现结论

#include<cstdio>
#include<iostream>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=1000+10;
int sg[4][maxn];//0 diff 1 same 2 free1 3 free2
bool bz[maxn];
int i,j,k,l,t,n,m;
int main(){
    n=1000;
    sg[0][1]=0;
    sg[1][1]=1;
    sg[2][1]=1;
    sg[3][1]=1;
    fo(i,2,n){
        bz[sg[1][i-1]]=1;
        fo(j,1,i-2){
            k=i-j-1;
            bz[sg[0][j]^sg[1][k]]=1;
        }
        fo(j,0,n+1)
            if (!bz[j]) break;
        sg[0][i]=j;
        fill(bz,bz+n+2,0);
        bz[sg[0][i-1]]=1;
        fo(j,1,(i-1)/2){
            k=i-j-1;
            bz[sg[0][j]^sg[0][k]]=1;
            bz[sg[1][j]^sg[1][k]]=1;
        }
        fo(j,0,n+1)
            if (!bz[j]) break;
        sg[1][i]=j;
        fill(bz,bz+n+2,0);
        bz[sg[2][i-1]]=1;
        bz[sg[1][i-1]]=1;
        bz[sg[0][i-1]]=1;
        fo(j,1,i-2){
            k=i-j-1;
            bz[sg[0][k]^sg[2][j]]=1;
            bz[sg[1][k]^sg[2][j]]=1;
        }
        fo(j,0,n+1)
            if (!bz[j]) break;
        sg[2][i]=j;
        fill(bz,bz+n+2,0);
        bz[sg[2][i-1]]=1;
        fo(j,1,i-2){
            k=i-j-1;
            bz[sg[2][j]^sg[2][k]]=1;
        }
        fo(j,0,n+1)
            if (!bz[j]) break;
        sg[3][i]=j;
        fill(bz,bz+n+2,0);
    }
    //fo(i,1,20) printf("sg[0][%d]:%d\n",i,sg[0][i]);
    //fo(i,1,20) printf("sg[1][%d]:%d\n",i,sg[1][i]);
    //fo(i,1,20) printf("sg[2][%d]:%d\n",i,sg[2][i]);
    //fo(i,1,20) printf("sg[3][%d]:%d\n",i,sg[3][i]);
}

结论

sg[0,i]=0
sg[1,i]=1
sg[2,i]=i
sg[3,i]=i%2
这是通过打表发现的,而事实证明很多博弈论SG函数的题目的结论都是推理总结打表找规律,可以节约时间,所以这里不予证明(其实证明并不难)。
接下来我们有了sg函数,就可以求了。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int i,j,k,l,t,n,m,ca;
bool czy;
char s[100000+10],ch;
int main(){
    freopen("game.in","r",stdin);
    while (scanf("%d",&n)!=EOF){
        do{
            s[1]=getchar();
        }while (s[1]<'0'||s[1]>'2');
        fo(i,2,n) scanf("%c",&s[i]);
        czy=0;
        fo(i,1,n){
            if (s[i]!='0'){
                czy=1;
                break;
            }
        }
        if (!czy){
            if (n%2) printf("FIRST\n");else printf("SECOND\n");
        }
        else{
            fo(i,1,n)
                if (s[i]!='0') break;
            j=i;
            fd(i,n,1)
                if (s[i]!='0') break;
            k=i;
            t=(j-1)^(n-k);
            fo(i,j+1,k){
                if (s[i]!='0'){
                    if (i-j>1&&s[j]==s[i]) t^=1;
                    j=i;
                }
            }
            if (t) printf("FIRST\n");else printf("SECOND\n");
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值