Codeforces Round #184 (Div. 2) E. Playing with String(博弈)
题目大意
两个人轮流在一个字符串上删掉一个字符,没有字符可删的人输掉游戏
删字符的规则如下:
1. 每次从一个字符串中选取一个字符,它是一个长度至少为 3 的奇回文串的中心
2. 删掉该字符,同时,他选择的那个字符串分成了两个独立的字符串
现在问,先手是否必胜,如果先手必胜,输出第一步应该删掉第几个字符,有多解的话,输出序号最小的那个
字符串的长度不超过5000,只包含小写英文字母
做法分析
可以这样考虑:将所有的长度大于等于 3(其实只需要找长度为 3 的就行)的奇回文串的中心标记出来
我们将连续的中心视为一个片段,那么,显然,游戏是进行在片段上面的,所以,游戏被分解成了多个子游戏的和
对于每一个片段(子游戏),我们考虑它的 sg 函数,显然,sg 函数只和这个片段的长度有关,于是定义状态 sg[len] 表示长度为 len 的片段的 sg 值。怎么得到当前状态的下一子状态呢?
如果删掉的是片段的两端,子状态分成了一个长度为 0 和长度为 len-2 的子片段
如果删掉的是片段的中间,子状态分成了一个长度为 i 和长度为 len-3-i 的子片段
这样,暴力出 sg 值,再依次的枚举第一次删掉的字符就可以解决这题了
参考代码
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 5 using namespace std; 6 7 const int N=5006; 8 9 char buff[N]; 10 int sg[N]; 11 12 int GET_SG(int len) { 13 if(sg[len]!=-1) return sg[len]; 14 bool vs[N]; 15 memset(vs, 0, sizeof vs); 16 vs[GET_SG(len-2)]=1; 17 for(int i=1; i+i<len; i++) vs[GET_SG(i-1)^(GET_SG(len-2-i))]=1; 18 for(int i=0; i<N; i++) if(!vs[i]) return sg[len]=i; 19 } 20 21 int hehe(int L, int R) { 22 int sum=0; 23 for(int i=L+1; i<R; i++) if(buff[i-1]==buff[i+1]) { 24 int id=i; 25 while(i<R && buff[i+1]==buff[i-1]) i++; 26 sum^=sg[i-id]; 27 } 28 return sum; 29 } 30 31 int main() { 32 memset(sg, -1, sizeof sg); 33 sg[0]=0, sg[1]=1; 34 for(int i=2; i<N; i++) if(sg[i]==-1) GET_SG(i); 35 while(scanf("%s", buff)!=EOF) { 36 bool flag=1; 37 for(int i=1, len=(int)strlen(buff); i<len-1; i++) { 38 if(buff[i-1]!=buff[i+1]) continue; 39 if(hehe(0, i-1)^hehe(i+1, len-1)) continue; 40 printf("First\n%d\n", i+1); 41 flag=0; 42 break; 43 } 44 if(flag) printf("Second\n"); 45 } 46 return 0; 47 }
题目链接 & AC 通道
Codeforces Round #184 (Div. 2) E. Playing with String