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
%d
", i+1);
41             flag=0;
42             break;
43         }
44         if(flag) printf("Second
");
45     }
46     return 0;
47 }
E. Playing with String

题目链接 & AC 通道

Codeforces Round #184 (Div. 2) E. Playing with String

原文地址:https://www.cnblogs.com/zhj5chengfeng/p/3254613.html