LightOJ 1401 No More Tic-tac-toe (博弈论 sg函数)

题目链接:
http://lightoj.com/volume_showproblem.php?problem=1401

题目大意:给n个格子,双方轮流往里面填’X’或者’O’,要求两个’X’或者两个’O’不能挨着。不能填的一方为负。题目给出一个残局,问先手是否有必胜策略。

分析:
n个格子被已经有的’X’和’O’分成了若干段,并且这些段互不影响(对其中任何一段的骚操作不会影响到其他段),玩家每次任选一段并对其进行操作。因此每段都可以看成一个子游戏,整体就是多个子游戏的和,满足博弈论sg函数的使用前提。

以前遇到的sg函数一般都是一维的,因为只有一个因素影响到sg值(比如取石子游戏,影响sg值的只有石子的个数)。而这个游戏中将每个空白段当做一个子游戏,那么影响sg值的因素就有三个:空白段的长度、空白段左端是什么、空白段右端是什么。因此开一个sg[n][3][3] 记录长度为n,左端为j,右端为k的一段的sg值。其中第二维和第三维0代表边界,1代表’X’,2代表’O’。
所以"…"就是sg[3][0][0],"X…O"就是sg[3][1][2]。

然后开始推sg值。长度为0时sg值肯定为0,因为此时不能往里面填任何东西了。长度为1时除了X.O和O.X是P态,其他都是N态。长度为2时,有两个空格可以填,因此有两个后继状态。比如X…O,就有两个后继状态XO.O和X.XO,因此后继状态长度减一,sg[2][1][2]=mex{sg[1][2][2],sg[1][1][1]}=0

长度大于2时,会发现在中间填一个字母后会把当前端分成左右两部分,此时的sg值等于左子段的sg值与右子段的sg值进行异或。(比如X . . . . O的其中一个后继状态是X . X . . O,若是把他当成一个单独的游戏,想计算他的sg值那就是把他子游戏X . X 和X . . O的sg值进行异或。这样就得到了
X . . . . O的其中一个后继状态的sg值。然后将他所有后继状态的sg值进行mex就行了)。当然,长度为1时也可以看成是分成了左右两个长度都为0的后继状态的异或。

可以得到sg值的计算公式:
sg[n][j][k]=mex{sg[n-1][x][k] , sg[n-1][j][y] , sg[1][j][z] ^ sg[n-2][z][k] , sg[2][j][z] ^ sg[n-3][z][k] , … , sg[n-2][j][z] ^ sg[1][z][k] }
其中x,y,z=0,1,2&&x!=j&&y!=k

对字符串处理时,可以用一个vector记录下每个分隔字符的下标和分隔字符是什么。先push_back一个(-1,0),表示左边界(下标-1,边界是0),然后如果str[i]=‘X’,就push_back一个(i,1)。最后再push_back一个(len,0)表示右边界。然后遍历vector,计算sg值并异或就行了。并且根据当前残局的字母数量确定现在是该先手行动还是该后手行动了。

代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#define N 105

using namespace std;

int sg[N][3][3];
int Hash[N];
vector<pair<int,int> >v;

void getSg()
{
    sg[1][0][0]=sg[1][0][1]=sg[1][0][2]=sg[1][1][0]=sg[1][1][1]=sg[1][2][0]=sg[1][2][2]=1;
    for(int i=2;i<N;i++)
        for(int j=0;j<3;j++)
            for(int k=0;k<3;k++)
            {
                memset(Hash,0,sizeof(Hash));
                if(j==2)
                    Hash[sg[i-1][1][k]]=1;
                else if(j==1)
                    Hash[sg[i-1][2][k]]=1;
                else
                {
                    Hash[sg[i-1][1][k]]=1;
                    Hash[sg[i-1][2][k]]=1;
                }
                if(k==2)
                    Hash[sg[i-1][j][1]]=1;
                else if(k==1)
                    Hash[sg[i-1][j][2]]=1;
                else
                {
                    Hash[sg[i-1][j][1]]=1;
                    Hash[sg[i-1][j][2]]=1;
                }
                for(int l=1;l<=i-2;l++)
                    Hash[sg[l][j][1]^sg[i-l-1][1][k]]=Hash[sg[l][j][2]^sg[i-l-1][2][k]]=1;
                for(int l=0;l<N;l++)
                    if(!Hash[l])
                    {
                        sg[i][j][k]=l;
                        break;
                    }
            }
}

int main()
{
    getSg();
    int t;
    char str[N];
    scanf("%d",&t);
    getchar();
    for(int tt=1;tt<=t;tt++)
    {
        scanf("%s",str);
        if(strlen(str)==1)
        {
            printf("Case %d: Yes\n",tt);
            continue;
        }
        v=vector<pair<int,int> >();
        int ans=0;
        v.push_back(make_pair(0,-1));
        for(unsigned int i=0;i<strlen(str);i++)
            if(str[i]=='X')
                v.push_back(make_pair(1,i));
            else if(str[i]=='O')
                v.push_back(make_pair(2,i));
        v.push_back(make_pair(0,strlen(str)));
        for(unsigned int i=0;i<v.size()-1;i++)
            ans^=sg[v[i+1].second-v[i].second-1][v[i].first][v[i+1].first];
        if((ans&&v.size()%2==0)||(!ans&&v.size()%2))
            printf("Case %d: Yes\n",tt);
        else
            printf("Case %d: No\n",tt);
    }
    return 0;
}

当然,打完表后就发现sg值的规律了。
下面是sg值的表:

for(int i=0;i<10;i++)
    {
        for(int j=0;j<3;j++)
        {
            for(int k=0;k<3;k++)
                printf("%d ",sg[i][j][k]);
            printf("\n");
        }
        printf("\n");
    }
0 0 0
0 0 0
0 0 0

1 1 1
1 1 0
1 0 1

0 2 2
2 1 0
2 0 1

1 3 3
3 1 0
3 0 1

0 4 4
4 1 0
4 0 1

1 5 5
5 1 0
5 0 1

可以看出,
sg[n][0][0]=n%2,
sg[n][0][1]=sg[n][0][2]=sg[n][1][0]=sg[n][2][0]=n,
sg[n][1][2]=sg[n][2][1]=0,
sg[n][1][1]=sg[n][2][2]=1
所以其实不用打表的,sg值直接写就行…
但我懒得改代码了,就这样吧

高哥牛批!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值