链接:http://acm.hdu.edu.cn/showproblem.php?pid=5724
题意:有一个n行20列的棋盘,棋盘上分布着一些棋子,A、B两人轮流下棋,A先手,每次操作可以将某个棋子放到自己右边的第一个空位(也就是说右边如果已经有子,可以跳过它,没有就右移一步),但最多20列,绝对不能超过棋盘,无棋可走的输。
思路:
典型的博弈论,太久没做这种题了,比赛的时候居然忘了sg值,单纯在弄必胜必败了。
首先看一行的情况下,有子记为1,无子记为0,那么一行的棋子分布就可以看作一个二进制数,所有情况就是1到2^20-1。那么我们用一个u[i]来表示i这种情况的sg值,1是必败点,u[1]=0,然后我们遍历2到2^20-1,判断每种情况可能一步到达的情况,比如10010,下一步可能是01010,10001,如果下一步都是必胜点,那么这一点肯定是必败的sg值为0,否则计算它的sg值,也就是不属于所有下一种情况的sg值且最小,比如下一步的sg值有0,1,2,4,5,那么不属于这些的又最小的就是3。之所以可以从2遍历到2^20-1,原因就是下一种情况肯定不比现在的情况大(这一点很重要)。
单行的解决了,多行只要把每一行的sg值异或一下就可以了。
1 #include<stdio.h> 2 #include<string.h> 3 int u[1050000]; 4 int dd[100]; 5 void solve(int x) 6 { 7 int tmp=x; 8 int bt[100],bo=0; 9 while(x) 10 { 11 bt[bo++]=x%2; 12 x/=2; 13 } 14 int pos=-1; 15 memset(dd,0,sizeof(dd)); 16 for(int i=0;i<bo;i++) 17 { 18 if(bt[i]==1&&pos==-1); 19 else if(bt[i]==1) dd[u[tmp+(1<<pos)-(1<<i)]]=1; 20 else pos=i; 21 } 22 for(int i=0;i<100;i++) 23 if(!dd[i]) 24 { 25 u[tmp]=i; 26 break; 27 } 28 } 29 void Init() 30 { 31 u[0]=0; 32 u[1]=0; 33 for(int i=2;i<=(1<<20);i++) 34 { 35 solve(i); 36 } 37 } 38 int main() 39 { 40 Init(); 41 int n,t,m,x; 42 scanf("%d",&t); 43 while(t--) 44 { 45 int s=0; 46 scanf("%d",&n); 47 for(int i=0;i<n;i++) 48 { 49 scanf("%d",&m); 50 int o=0; 51 for(int i=0;i<m;i++) 52 { 53 scanf("%d",&x); 54 o=o+(1<<(20-x)); 55 } 56 s^=u[o]; 57 } 58 if(s) printf("YES "); 59 else printf("NO "); 60 } 61 return 0; 62 }