题目描述
AKN玩游戏玩累了,于是他开始和同伴下棋了,玩的是跳棋!对手是wwx!这两位上古神遇在一起下棋,使得棋局变得玄幻莫测,高手过招,必有一赢,他们都将用最佳策略下棋,现在给你一个n*20的棋盘,以及棋盘上有若干个棋子,问谁赢?akn先手!

游戏规则是这样的:

对于一个棋子,能将它向右移动一格,如果右边有棋子,则向右跳到第一个空格,如果右边没有空格,则不能移动这个棋子,如果所有棋子都不能移动,那么将输掉这场比赛。
样例输入:

2
1
2 19 20
2
1 19
1 18

输出:

NO
YES

题意:

这个题我看了一阵子才明白题意,每个棋子只能想右走,当走到头(也就是第20格)就不能再移动了,当右边的格已经有棋子时,可以跳过这个棋子走到下一个格,但是如果这个棋子右边没有格那就不能走了,就比如样例第一个例子,19 20两个紧挨着的棋子,位于棋盘最后,就都移动不了

题解:

经典博弈论
我看到两个做法:

方法一.

我们先考虑120的情况
如果我们将所有空格编号,将空格之间相连的棋子看做一层,什么意思?就是我们将连续的块看做一层,不连续的看做不同层,这样从左到右形成x层,然后所有棋子要向右移,就是从上层向下层移动,当全部移动到第0层,也就是最后,游戏结束
这样问题就变成了阶梯nim问题
在阶梯nim问题中,我们只需要考虑位置为奇数的ai的异或和
在本题中,我们只需要考虑层数为奇数的棋子数量
求出1
20的答案后,将所有答案异或就是最终答案(sg定理)

代码:

#include<cstdio>
#include<cstring>
int T,N,K,cnt,tot,x,ans1,ans2;
bool hv[23];//hv[i]==true表示i位置有石子
int main()
{
    scanf(" %d",&T);
    while(T--)
    {
        scanf(" %d",&N);
        ans2=0;//整个数据的SG值用ans2储存
        while(N--)
        {
            scanf(" %d",&K);
            memset(hv,false,sizeof(hv));
            cnt=20-K+1;//空格数量+1 
            tot=0;
            ans1=0;//cnt即C,tot储存当前阶梯棋子个数,ans1储存本行SG值
            while(K--)
            {
                scanf(" %d",&x);
                hv[x]=true;//标记有石子
            }
            for(int i=1;i<=20;++i)
            {
                if(!hv[i])//如果是空格 
                {
                    cnt--;//空格数量减1 
                    if((cnt)&1)ans1^=tot;//奇数级阶梯,异或
                    tot=0;
                }
                else ++tot;//加棋子到阶梯上
            }
            ans2^=ans1;//SG定理应用
        }
        if(ans2)printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

方法二:

sg函数大家应该都知道
对于任意一个状态x,如果sg(x)=0说明x为必败态,!=0为必胜态
sg(x)为不属于其所有后续状态的sg函数值集合的最小非负整数
sg(x)=mex(S),其中S是x的后续状态的sg函数值集合
比如x有5个后续状态,其中sg函数值分别是1,5,2,0,4,那么sg(x)=3
对于棋盘的状态,我们完全可以用二进制来表示,比如10001,表示第1位和第5位有棋子
题目中有20个位置,我们规定最右边的位置是第0位,最左边是第19位
对于题目给的每种状态x,我们一次寻找x的后续状态S的值,然后利用sg(x)=mex(S)的结论得到答案
那怎么查找x的后续状态S?
我们从x状态的最高位开始,如果第i格有棋子,我们就让该棋子移动到第i-1格上,也就是让当前棋子向后移动一位,依次递归,当然第0位的就不用移动
如果有两个棋子靠着也没事,按照我们的设计,最右边的棋子会向右移动,这样两个棋子就不靠着了

代码:

#include<iostream>
#include<cstring>
#define MAXN (1<<20)-1
using namespace std;

int sg[MAXN+5];

int dfs(int x){
    //cout<<x<<endl;
    if(sg[x]!=-1) return sg[x];

    bool vis[25];
    memset(vis, 0, sizeof(vis));

    int last0 = -1;
    for(int i=0;i<20;i++){
        if((x>>i)&1){
            if(last0==-1) continue;
            vis[dfs(x^(1<<i)^(1<<last0))] = 1;
        }
        else last0 = i;
    }

    for(int i=0;i<20;i++){
        if(!vis[i]) return sg[x] = i;
    }
}

int main(){
    memset(sg,-1,sizeof(sg));

    int T,N,m;
    int t;
    cin>>T;
    while(T--){
        int ans = 0;
        cin>>N;
        for(int i=1;i<=N;i++){
            int x = 0;
            cin>>m;
            while(m--){
                cin>>t;
                x += (1<<(20-t));
            }
            ans ^= dfs(x);
        }
        //cout<<ans;
        if(ans) cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }

    return 0;
}