题目大概说有n行,每行20格子,都有一些棋子,两个人轮流进行这个操作:选择某一行一个棋子移动到该行右边第一个空的格子。不能进行的人输。问先手是否能赢。
思路:此题,每一行可以单独成一个游戏,每一行互不影响,所以我们可以转化为 NIM 来做,首先我们就要求 sg 函数的值。 我们先预处理出来每行的状态,每一行有20个棋子,所以有 1 << 19 个状态,我们把这个多状态全部弄出来,就可以亦或求值了。
可以确定的是,在每一行,如果我们没有办法走的话,那么这个sg的值就是0。我们枚举每个状态,再找这个状态可以怎么走。因为是二进制压位,所以我们要找1 的位置,让她向右移动到空位置。这个当前状态可以走的后继点,然后我们把后继点的sg值加入到集合中,(也可以用bool 数组,把这个点记录下来。)然后把所有状态枚举完,
后面就是读入,看看每一行的状态是什么,然后 亦或。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n,m;
int sg[1 << 20];
void init(){
for (int i = 0; i < (1 << 20); i++){
int vis[24] = {0};
int k = 100;
for (int j = 19; j >= 0; j--){
if ((i & (1 << j)) == 0) continue;
if (k <= j-1){
vis[sg[i ^ (1 << j) ^ (1 << k)]] = 1;
} else
{
for (int s = j - 1; s >= 0; s--){
if ((i&(1 << s)) == 0){
k = s;
vis[sg[i ^(1 << j) ^ (1 << k)]] = 1;
break;
}
}
}
}
for (int j = 0; j <= 20; j++)
if (vis[j] == 0) {
sg[i] = j;
break;
}
}
}
int main(){
int T;
cin>>T;
init();
while(T--){
scanf("%d",&n);
int ans = 0,x;
for (int i = 0; i < n; i++){
scanf("%d",&m);
int y = 0;
for (int j = 0; j < m; j++){
scanf("%d",&x);
y = y | (1 << (20 -x));
}
ans = ans ^ sg[y];
}
if (ans) printf("YES\n"); else
printf("NO\n");
}
return 0;
}