题目:hdu5724
题意:给定一个n*20的棋盘,棋盘上有若干棋子。如果一颗棋子右侧为空,则只可以向右移动一格,若非空,则可以移到第一个空的位置,两人轮流操作,不能操作者为输,问先者是否有必胜策略。
解答:注意到棋盘只有20列,则可以用状态压缩存储当前的状态。预处理出所有状态的sg值。然后n个抑或一下就好了。
我的代码(每次递归求出当前的sg值):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAXN = 1 << 21;
int sg[MAXN];
int getnew(int a,int u,int v)
{
return (a - (1 << u) + (1 << v));
}
int getsg(int a)
{
if(sg[a] != -1)
return sg[a];
int vis[20];
memset(vis,0,sizeof(vis));
int ok = 1;
for(int i = 0;i < 19;i++)
{
int temp = 1 << i;
if(a & temp)
{
for(int j = i + 1;j <= 19;j++)
{
if(!(a & (1 << j)))
{
vis[getsg(getnew(a,i,j))] = 1;
break;
}
}
}
}
for(int i = 0;;i++)
{
if(!vis[i])
{
sg[a] = i;
return i;
}
}
}
void init()
{
memset(sg,-1,sizeof(sg));
for(int i = 0;i <= ((1 << 20) - 1);i++)
{
sg[i] = getsg(i);
}
}
int main()
{
int T,t,n;
scanf("%d",&T);
init();
while(T--)
{
int ans = 0;
scanf("%d",&n);
while(n--)
{
int k;
int tmp = 0;
scanf("%d",&k);
while(k--)
{
scanf("%d",&t);
tmp += 1<<(t-1);
}
ans ^= sg[tmp];
}
if(ans == 0)
puts("NO");
else
puts("YES");
}
return 0;
}
别人的代码(从后往前求sg值。因为求状态值较小的值,它的后继状态的值肯定比当前的状态值大。所以从后往前求就可以了,不需要递归)
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
const int MAXN = 1 << 20;
int n;
int sg[MAXN];
int vis[20];
int getsg(int x)
{
memset(vis,0,sizeof(vis));
for(int i = 0;i < 20;i++)
{
if(x&(1 << i))
{
for(int j = i + 1;j < 20;j++)
{
if(!(x&(1 << j)))
{
vis[sg[x^(1 << i)^(1 << j)]] = 1;
}
}
}
}
for(int i = 0;i < 20;i++)
if(!vis[i])
return i;
}
int main()
{
int t;
scanf("%d",&t);
for(int i = (1 << 20)-1;i >= 0;i--)
sg[i] = getsg(i);
while(t--)
{
int n;
scanf("%d",&n);
int ans = 0;
for(int i = 0;i < n;i++)
{
int q;
scanf("%d",&q);
int m = 0;
while(q--)
{
int x;
scanf("%d",&x);
m = m | (1 << (x-1));
}
ans = ans ^ sg[m];
}
if(ans)
puts("YES");
else
puts("NO");
}
return 0;
}