WIN
大致题意:题目的大概意思是这样的,就是说给你一个网格纸,大小w*h,让你剪,一人剪一次;随着一次次的剪,纸的数量越来越多,但大小越来越小,谁先剪出1*1的纸张谁就赢。这边其实是Nim博弈的变形,是的你没有看错,就是尼姆博弈的变形,不过就是变得有点大了,变得面目全非了。
何为Nim博弈,大牛见笑了,说给刚接触算法的同学听,所谓的尼姆博弈就是给你n堆石子,数量为a1, a2, a3,...an你从任意一堆里选择任意个,谁没得取谁就输。这一个是经典的题了,直接给结论,当满足a1^a2^a3^..an==0你就输了。就是每一堆的石子数的总亦或值为0的话就是输了。
回到问题上来,如何跟Nim博弈等价起来呢?这边起先只有一张纸,就是相当于只有一堆石子。这边引入一个grundy数的概念,我们现在只以一堆石子为例,其实这边可以说是状态的转移,一堆x个石子,一次可以转化为x-1,x-2, x-3..3, 2, 1.所以grundy值在这边就隐含的表示一张纸对应的石子数,一张纸可以经过剪变成两张纸,这两张纸也对应的各自的grundy数,这是就是一堆石子现在居然变成两堆了。但目前这个状态其实只要取这两个数亦或值又变成一堆。相当于Nim博弈两堆石子亦或可以只当成一堆。
接下来是关键怎么计算grundy数。一个状态的grundy数其实就是他能转移到的状态的grundy值除外的最小非负整数。为什么是这样的呢?首先Nim堆中石子x可以转化为x-1, x-2, x-3, ..2, 1.设当前的状态grundy数为y,那么他一定样要可以转化成y-1, y-2, ...3, 2, 1。举几个例子,比如当前状态可以转化成grundy数为1, 2, 3的状态,那么当前状态grundy数一定是4,这样才能转化为最小的。如果为3 ,5那么当前只能是0。就是说白了就是一个数,比这个数小的数他都能通过变化得到。可能有的人会有疑问,比如可以转化为3, 5的grundy值只能为0,但这个0其实可以变成3或5,这不就说明石子数量会增多吗?其实不然,这个时候先手将grundy值变大了,后手一定可以恢复原来的值,为什么呢?因为每一个grundy数根据他的求法可得一定是可以变为比他小的任意数,这不就说明石子数从变多又可以恢复原样了。
把输入的w*h只当做一堆石子来处理对应的grundy值为0时,就意味着输了。
AC代码:
# include <cstdio> # include <cstring> # include <set> using namespace std; int grundy[210][210]; int main(){ int w, h, i, j, k, temp; for(i=2; i<=200; i++){ for(j=2; j<=200; j++){ int vis[1005]; memset(vis, 0, sizeof(vis)); //当出现w==1或h==1是就以为你输了就没得转移了 for(k=2; i-k>=2; k++){//枚举可以转移到的转态 vis[grundy[k][j]^grundy[i-k][j]]=1; } for(k=2; j-k>=2; k++){//枚举可以转移到的转态 vis[grundy[i][k]^grundy[i][j-k]]=1; } grundy[i][j]=0; for(k=0; ;k++) if(vis[k]==0){//计算对应的grundy数 grundy[i][j]=k; break; } } } while(scanf("%d%d", &w, &h)!=EOF){ if(grundy[w][h]) printf("WIN\n"); else printf("LOSE\n"); } return 0; }