题意:
有一个n行20列的棋盘,对于每行棋子的操作:如果当前棋子相邻的右边没棋子,则当前棋子可以直接移到其右边,如果其右边有若干连续的棋子就跳过这些棋子放到其后面的第一个空位,Alice先手,最后无法操作者输,给出n代表棋盘由n行,以及每行棋子初始位置,问先手是否必胜。
思路:
因为列的状态只有20个,所以状压一下,枚举所有状态,将所有状态的SG值预处理到SG数组中。
由于本题每一个状态的后继状态最多不会超过20个,所以mex数组当然要开小点了,开了个1e6的mex数组果然是找死...
另外补充:memset比普通循环赋值只优化了常数级别,复杂度仍然是O(n);
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1<<20;
int mex[25], SG[maxn], a[25];
void GetSG()
{
int i, j, k, tmp;
SG[1] = 0;
for(i = 2; i < maxn; ++i)
{
memset(mex, 0, sizeof mex);
tmp = -1;
for(j = 0; j < 20; ++j)
{
if(!(i>>j)) break;
if(!((i>>j)&1)) tmp = 1<<j; //记录最近的状态为0的位置的值
else if(tmp != -1) mex[SG[i-(1<<j)+tmp]] = 1;
}
for(j = 0 ;; ++j)
if(!mex[j]) break;
SG[i] = j;
}
}
int main()
{
//freopen("in.txt", "r", stdin);
int t, n, m, x, k, ans;
GetSG();
scanf("%d", &t);
while(t--)
{
scanf("%d", &n); ans = 0;
for(int i = 1; i <= n; ++i)
{
scanf("%d", &m); k = 0;
memset(a, 0, sizeof a);
for(int j = 1; j <= m; ++j)
{
scanf("%d", &x);
a[20-x+1] = 1; //令最右边为低位
}
for(int i = 1; i <= 20; ++i)
if(a[i]) k += (1<<(i-1));
ans ^= SG[k];
}
if(ans) puts("YES");
else puts("NO");
}
return 0;
}
继续加油~