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