(博弈/sg)高手过招

https://www.luogu.org/problemnew/show/P2575
AKN玩游戏玩累了,于是他开始和同伴下棋了,玩的是跳棋!对手是wwx!这两位上古神遇在一起下棋,使得棋局变得玄幻莫测,高手过招,必有一赢,他们都将用最佳策略下棋,现在给你一个n*20的棋盘,以及棋盘上有若干个棋子,问谁赢?akn先手!
游戏规则是这样的:
对于一个棋子,能将它向右移动一格,如果右边有棋子,则向右跳到第一个空格,如果右边没有空格,则不能移动这个棋子,如果所有棋子都不能移动,那么将输掉这场比赛。

考虑每行为一个单独的sg博弈,n行sg值异或即可。将每行的状态以二进制表示,1表示存在棋子,0表示不存在。对每个状态去找他可以到的子状态,求sg值即可。注意可以预先预处理优化

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1000 + 5;
int sg[1 << 20];
bool vis[25]; //每个状态最多由19个状态转移而来
int Sg(int state) {
    memset(vis, 0, sizeof(vis));
    int tmp = 1, nxt = 0;
    for(register int i = 1; i <= 20; i++) {
        if(state & tmp) { //如果该位置有棋子
            if((state | (tmp >> 1)) != state) vis[sg[state ^ tmp ^ (tmp >> 1)]] = 1; 
            //该位置的右边无棋子
            else if(nxt) vis[sg[state ^ tmp ^ (1 << (nxt - 1))]] = 1;
        }
        else nxt = i; //保存跳过棋子可以到达的位置
        tmp <<= 1; //枚举每个位置
    }
    tmp = 0;
    while(vis[tmp]) ++tmp;
    sg[state] = tmp;
}
int main()
{
    for(register int i = 0; i < (1 << 20); i++) Sg(i); //预处理每个可能状态
    int t, n, m, pos, ans;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        ans = 0;
        for(register int i = 0; i < n; i++) {
            scanf("%d", &m);
            int state = 0;
            for(register int j = 0; j < m; j++) {
                scanf("%d", &pos);
                state += 1 << (20 - pos);
            }
            ans ^= sg[state];
        }
        printf("%s\n", ans == 0 ? "NO" : "YES");
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_40588429/article/details/83989675