如果已经有三个相邻的X,则先手已经输了。
如果有两个相邻的X或者两个X相隔一个.,那么先手一定胜。
除去上面两种情况,每个X周围两个格子不能再放X了,因为放完之后,对手下一轮再放一个就输了。
最后当“禁区”布满整行,不能再放X了,那个人就输了。
每放一个X,禁区会把它所在的线段“分割”开来,这若干个片段就可以看做若干个游戏的和。
设g(x)表示x个连续格子对应的SG函数值,递推来求g(x):
g(x) = mex{ g(x-3), g(x-4), g(x-5), g(x-6) xor g(1), g(x-7) xor g(2)... }
g(0) = 0, g(1) = g(2) = g(3) = 1
枚举策略时,就是模拟下一步的状态,记录其中所有必败状态。
1 #include <cstdio> 2 #include <cstring> 3 4 const int maxn = 200; 5 char s[maxn + 10]; 6 int g[maxn + 10], ans[maxn + 10]; 7 bool vis[maxn + 10]; 8 9 bool winning(const char* s) 10 { 11 int n = strlen(s); 12 for(int i = 0; i < n-2; i++)//已经有三个相邻的X,先手输 13 if(s[i] == 'X' && s[i+1] == 'X' && s[i+2] == 'X') return false; 14 15 bool no[n+1]; 16 memset(no, false, sizeof(no)); 17 for(int i = 0; i < n; i++) if(s[i] == 'X') 18 { 19 for(int d = -2; d <= 2; d++) 20 { 21 if(i+d >= 0 && i+d < n) 22 { 23 if(d != 0 && s[i+d] == 'X') return true;//有两个X在彼此的禁区,先手胜 24 no[i+d] = true;//设置禁区 25 } 26 } 27 } 28 29 no[n] = 1; 30 int sg = 0; 31 for(int i = 0; i < n; i++) 32 { 33 if(no[i]) continue; 34 int cnt = 0; 35 while(i < n && !no[i]) { i++; cnt++; } 36 sg ^= g[cnt]; 37 } 38 return sg != 0; 39 } 40 41 int main() 42 { 43 //freopen("in.txt", "r", stdin); 44 45 g[0] = 0; 46 g[1] = g[2] = g[3] = 1; 47 for(int i = 4; i <= maxn; i++) 48 {//递推求函数g 49 memset(vis, false, sizeof(vis)); 50 for(int j = 3; i-j >= 0; j++) 51 { 52 int v = 0; 53 v ^= g[i-j]; 54 int x = j - 5; 55 if(x > 0) v ^= g[x]; 56 vis[v] = true; 57 58 for(int j = 0; ; j++) if(!vis[j]) { g[i] = j; break; } 59 } 60 } 61 62 int T; 63 scanf("%d", &T); 64 while(T--) 65 { 66 scanf("%s", s); 67 if(!winning(s)) { printf("LOSING "); continue; } 68 69 puts("WINNING"); 70 int n = strlen(s); 71 memset(ans, 0, sizeof(ans)); 72 int p = 0; 73 for(int i = 0; i < n; i++) if(s[i] == '.') 74 { 75 s[i] = 'X'; 76 if(!winning(s)) ans[p++] = i+1;//后继必败状态便是先手下一步的策略 77 s[i] = '.'; 78 } 79 for(int i = 0; i < p; i++) 80 { 81 if(i) printf(" "); 82 printf("%d", ans[i]); 83 } 84 printf(" "); 85 } 86 87 return 0; 88 }