卧薪尝胆,厚积薄发。
高手过招
Date: Sat Oct 27 18:57:30 CST 2018 In Category: NoCategory

Description:

在一个 $n\times 20$ 的棋盘上,对于一个棋子,能将它向右跳到第一个空格,如果右边没有空格,则不能移动这个棋子,如果所有棋子都不能移动,那么将输掉这场比赛,问先手能否胜利。
$1\leqslant testcases\leqslant 100,1\leqslant n\leqslant 1000$

Solution:

每行之间是独立的,而一行最长只有 $20$ ,可以想到预处理每行答案。
感觉更加理解 $SG$ 函数了,如果只有一个游戏,那么可以只开一个 $bool$ 型变量表示这个局面是否能赢,如果后继状态都为赢,那这个状态就输,否则就赢,但是这样不能把游戏组合起来,所以就有了 $SG$ 函数,各个子游戏的 $SG$ 函数异或和就是总游戏的 $SG$ 值。
这个题目的话,先预处理出每个状况的 $SG$ 值,具体来说就是维护一个下一个空位,倒序枚举每个棋子往后跳并用 $mex$ 求 $SG$ ,然后只要把每一行的值异或起来判断是否为 $0$ 即可。

Code:


#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
#define MAXL 22
int SG[1 << MAXL];
bool vis[MAXL];
void calc_SG(int x)
{
memset(vis,false,sizeof(vis));
int nxt = 0;
for(int i = 1;i <= 20;++i)
{
if(x & (1 << (i - 1)))
{
if(nxt != 0)
{
vis[SG[x ^ (1 << (i - 1)) ^ (1 << (nxt - 1))]] = true;
}
}
else
{
nxt = i;
}
}
int k = 0;
for(;vis[k];++k);
SG[x] = k;
return;
}
int main()
{
for(int i = 1;i < (1 << 20);++i)calc_SG(i);
int testcases;
scanf("%d",&testcases);
while(testcases--)
{
int res = 0;
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++i)
{
int m;
scanf("%d",&m);
int p;
int tmp = 0;
for(int k = 1;k <= m;++k)
{
scanf("%d",&p);p = 20 - p + 1;
tmp |= (1 << (p - 1));
}
res ^= SG[tmp];
}
if(res != 0)puts("YES");
else puts("NO");
}
return 0;
}
In tag: 数学-SG函数
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡