poj2133(sg函数)
关于sg函数:http://www.cnblogs.com/Knuth/archive/2009/09/05/1561007.html
题目链接:http://poj.org/problem?id=2311
思路:
就我而言,首先遇到的第一个问题就是抽象不出博主在《Game theory初步》(在前面那个网址的基础上向前找博主的前几篇博客就可以找到)这篇中讲的ICG(公平组合游戏)模型,因为如果纸片出现了1*1就结束了,而其他的纸片却不一定都是1*1的纸片,如果我们把一个纸片抽象成一个游戏图上的点,也就是说有些点还可以决策,但游戏却已经结束了,这明显和ICG的定义是违背的。
后来看了dicuss之后恍然大悟,对于任意一个选手来讲,肯定不会优先生成1*n或者n*1的纸片,因为下一轮对方就可以取胜,因此,如果这样不断的剪下去的话,最终肯定有一个局面都是3*2、2*3、2*2的纸片,实际上这时就可以断定输赢了,同时如果我们把3*2、2*3、2*2都看成终止状态的话,就巧妙避免了上面所提到的问题,因为这样游戏终止时不会有尚未决策的点。
单单解决完上一个问题还是不够的,接下来我又遇到了另一个问题,就是之前只做过一些裸的类似Nim游戏的题目,这些题目很容易将游戏转化成图游戏(即一个或若干个棋子初始时在某些特定的位置,每次将一个棋子通过一条有向边移到另一个位置,当所有棋子都不能移动的时候,游戏就终止了),但这个问题就不一样了,如果我们将一个w*h的纸片看做点(w,h),假如一种切的方式是生成(a,b)和(c,d)两个纸片,那岂不成了当一个棋子在(w,h)这个点的时候,有一种决策是让这个棋子分裂成两个棋子,其中一个移到(a,b),另一个移到(c,d)么?这要怎么搞呢?我瞬间就晕了……
结果今天早晨起床的时候却突然悟到了一些,实际上也主要是这时思考的时候就把游戏和的概念联系起来一起思考了。对于位于(w,h)这个位置的棋子而言,如果变成两个分别位于(a,b)和(c,d)的棋子,这两个棋子是不能各自看成(w,h)的后继的,因为后继之间应该是相互独立的,即既可以通过决策到达其中一个位置,也可以通过决策到达另一个位置,所以我们要将这两个棋子合起来考虑,共同看成是(w,h)的一个后继。同时结合游戏和的一些知识,(a,b)以及(c,d)是不是可以看成(w,h)的两个子游戏呢?当然可以,因为后面无论是对(a,b)还是(c,d)进行决策,都合对(w,h)进行决策的规则是一样的。那么对于(a,b)和(c,d)共同看成一个后继来讲,这个后继的SG函数值就应该是SG(a,b)^SG(c,d)。
至此为止,我们就终于把这个剪纸片问题化归成和一些简单的Nim游戏类似的形式了,其余的求解过程就和一些简单的Nim游戏的求解过程一样了。
以上内容摘自博客:http://www.cnblogs.com/staginner/archive/2012/02/26/2368417.html
代码:
1 #include <stdio.h> 2 #include <string.h> 3 #define MAXN 210 4 using namespace std; 5 6 int sg[MAXN][MAXN]; 7 8 int dfs(int x, int y){ 9 int vis[MAXN]; 10 memset(vis, 0, sizeof(vis)); 11 if(sg[x][y]!=-1){//当前点已经走过 12 return sg[x][y]; 13 } 14 for(int i=2; i<=x-i; i++){//如果遍历到1,2的状态则必输,求赢自然可以从3开始遍历 15 vis[dfs(i, y)^dfs(x-i, y)]=1;//母图可以由子图异或得到 16 } 17 for(int i=2; i<=y-i; i++){ 18 vis[dfs(x, i)^dfs(x, y-i)]=1; 19 } 20 for(int i=0; ; i++){ 21 if(vis[i]==0){//求mex值 22 sg[x][y]=sg[y][x]=i; 23 return i; 24 } 25 } 26 } 27 28 int main(void){ 29 int n, m; 30 memset(sg, -1, sizeof(sg)); 31 sg[2][2]=sg[3][2]=sg[2][3]=0;//必输状态 32 while(~scanf("%d%d", &n, &m)){ 33 if(dfs(n, m)){ 34 puts("WIN"); 35 }else{ 36 puts("LOSE"); 37 } 38 } 39 return 0; 40 }