SG函数
为了更一般化博弈问题,我们引入SG函数
SG函数有如下性质:
1.如果某个状态SG函数值为0,则它后继的每个状态SG函数值都不为0
2.如果某个状态SG函数值不为0,则它至少存在一个后继的状态SG函数值为0
如果某个局面SG函数值为0,则该局面先手必败
放到有向图中,该有向图的核就是SG值为0的点构成的集合
游戏的和
游戏的和的SG函数值=所有子游戏SG函数值的异或和Xor
如果所有子游戏都进行完毕,那么Xor=0,必败
如果某个状态的SG函数值为0,那么后手一定可以做出一种动作,保持Xor=0,那么先手必败。
反之某个状态的SG函数值不为0,先手可以让Xor=0,变成后手,重复上述动作,那么先手必胜
这样就能轻松合并多个独立的组合游戏啦
SG组合游戏
NIM游戏
有n堆石子,两个人玩游戏,每次轮流在一堆里取走任意个,取走最后一堆的最后一个石子的人赢,问谁赢
一堆石子相当于一个子游戏,显然该子游戏的SG函数值为该堆中石子数
再根据游戏的和的思想,把子游戏合并就能求出谁赢了
NIMk游戏
同样是NIM游戏,现在变成了每次在k堆中取任意个
NIM游戏采取策略的根本是,保证当SG函数值为0时,不论先手如何操作,后手一定能做出一种动作,保持Xor=0
把每堆石子都转化成k+1进制数,再进行不进位的加法即可
POJ有3道题目的题解待填坑..
SG组合游戏变形
一些SG组合游戏的终止状态比较特殊,我们可以通过转化解决
POJ 3537 Crosses and Crosses
题目大意:给出一个1*n的网格,一开始全都是白格子,两个人轮流把一个白格子涂黑,谁先涂出来连续3个黑格子谁就赢了
游戏的结束状态不容易直接搞啊
我们剖析游戏本身的性质
先手把一个格子涂黑后,它左右一共连续5个格子(边界另外讨论)一定不能被后手涂
相当于每次涂黑一个格子,删掉连续的不超过5个格子,两侧剩下的格子构成了一个或两个子游戏
依次求出长度为1~n的连续格子的游戏的SG函数即可
1 #include <queue> 2 #include <cmath> 3 #include <vector> 4 #include <cstdio> 5 #include <cstring> 6 #include <algorithm> 7 #define N1 2010 8 #define M1 200010 9 #define ll long long 10 #define dd double 11 using namespace std; 12 const dd eps=1e-7; 13 14 int n,tl; 15 int sg[N1],use[N1],que[N1]; 16 17 int main() 18 { 19 scanf("%d",&n); 20 if(n==1){ puts("1"); return 0; } 21 if(n==2){ puts("2"); return 0; } 22 if(n==3){ puts("1"); return 0; } 23 if(n==4){ puts("1"); return 0; } 24 sg[0]=0; sg[1]=sg[2]=sg[3]=1; sg[4]=2; 25 int i,j; 26 for(i=5;i<=n;i++) 27 { 28 que[++tl]=sg[i-4]; que[++tl]=sg[i-3]; 29 for(j=0;j+5<=i;j++) que[++tl]=sg[j]^sg[i-j-5]; 30 for(j=1;j<=tl;j++) use[que[j]]=1; 31 for(j=0;j<=i;j++) if(!use[j]){ sg[i]=j; break; } 32 while(tl) use[que[tl--]]=0; 33 } 34 if(sg[n]>0) puts("1"); 35 else puts("2"); 36 return 0; 37 }
POJ 2311 Cutting Game
题目大意:给出一张n*m的纸,每次可以把它剪成两半,先剪出来1*1小纸片的人赢
虽然1*1是必败局面,但并不容易直接推出其他格子的SG函数
显然x>1时,1*x的局面先手必胜。而2*2局面必败,进而可以推出2*3,3*3也都是必败局面
利用这两点就可以轻松推出整张纸的SG函数了
1 #include <queue> 2 #include <cmath> 3 #include <vector> 4 #include <cstdio> 5 #include <cstring> 6 #include <algorithm> 7 #define N1 205 8 #define M1 200010 9 #define ll long long 10 #define dd double 11 using namespace std; 12 const dd eps=1e-7; 13 14 int T,n,m,de; 15 int sg[N1][N1],use[N1*2]; 16 17 int main() 18 { 19 int i,j,k,x,y,ans,flag; 20 for(i=1;i<=200;i++) sg[1][i]=1; 21 for(i=1;i<=200;i++) sg[i][1]=1; 22 for(i=2;i<=200;i++) for(j=2;j<=200;j++) //if(i+j>4) 23 { 24 for(k=2;k<j-1;k++) use[sg[i][k]^sg[i][j-k]]=1; //if(i+k>=4&&i+j-k+1>=4) 25 for(k=2;k<i-1;k++) use[sg[k][j]^sg[i-k][j]]=1; //if(j+k>=4&&j+i-k+1>=4) 26 27 for(k=0;k<=200;k++) if(!use[k]){ sg[i][j]=k; break; } 28 29 for(k=2;k<j-1;k++) use[sg[i][k]^sg[i][j-k]]=0; //if(i+k>=4&&i+j-k+1>=4) 30 for(k=2;k<i-1;k++) use[sg[k][j]^sg[i-k][j]]=0; //if(j+k>=4&&j+i-k+1>=4) 31 } 32 while(scanf("%d%d",&n,&m)!=EOF) 33 { 34 if(sg[n][m]) puts("WIN"); 35 else puts("LOSE"); 36 } 37 return 0; 38 }
BZOJ 1457 棋盘游戏
题目大意:给出一个坐标系,上面有很多个皇后,皇后只能向左/向下/向左下走,两个人轮流每次选择一个皇后移动,谁先把皇后移动到(0,0)谁赢
如果直接把(0,0)当做游戏终止局面的话,求解的问题就是谁先把所有皇后都移动到(0,0)谁赢了
显然如果存在x=y||x=0||y=0的皇后,先手必胜
所以两个人都极力避免自已移动出来上述三种情况的皇后
而皇后只能向左下移动,最终皇后一定集中在(1,2)和(2,1)
我们把SG函数为0的位置设为(1,2)和(2,1)即可
1 #include <queue> 2 #include <cmath> 3 #include <vector> 4 #include <cstdio> 5 #include <cstring> 6 #include <algorithm> 7 #define N1 105 8 #define M1 200010 9 #define ll long long 10 #define dd double 11 using namespace std; 12 const dd eps=1e-7; 13 14 int T,n; 15 int sg[N1][N1],use[N1]; 16 17 int main() 18 { 19 int i,j,k,x,y,ans,flag; 20 for(i=1;i<=100;i++) for(j=1;j<=100;j++) if(i!=j) 21 { 22 for(k=1;k<j;k++) if(k!=i) use[sg[i][k]]=1; 23 for(k=1;k<i;k++) if(k!=j) use[sg[k][j]]=1; 24 for(k=1;k<min(i,j);k++) use[sg[i-k][j-k]]=1; 25 26 for(k=0;k<200;k++) if(!use[k]){ sg[i][j]=k; break; } 27 28 for(k=1;k<j;k++) if(k!=i) use[sg[i][k]]=0; 29 for(k=1;k<i;k++) if(k!=j) use[sg[k][j]]=0; 30 for(k=1;k<min(i,j);k++) use[sg[i-k][j-k]]=0; 31 } 32 scanf("%d",&T); 33 while(T--) 34 { 35 scanf("%d",&n); ans=0,flag=0; 36 for(i=1;i<=n;i++) 37 { 38 scanf("%d%d",&x,&y); 39 if(x==y||!x||!y) flag=1; 40 ans^=sg[x][y]; 41 } 42 if(ans||flag) puts("^o^"); 43 else puts("T_T"); 44 } 45 return 0; 46 }
原文地址:https://www.cnblogs.com/guapisolo/p/10447575.html