【题解】Casting Spells LA 4975 UVa 1470 双倍回文 SDOI 2011 BZOJ 2342 Manacher

首先要吐槽LRJ,书上给的算法标签是“有难度,需要结合其他数据结构”,学完Manacher才发现几乎一裸题

题目的意思是问原串中有多少个wwRwwR这样的子串,其中wR表示w的反串

比较容易看出来,wwRwwR本身是一个回文串,wwR也是一个回文串

最裸的暴力是,我们枚举每一个回文串,然后判断这个回文串的左半边是不是也是个回文串

然后我们考虑用Manacher

我们考虑Manacher的工作原理,是在充分利用原先的信息的前提下,不重复,不遗漏的枚举每个回文串

也就是说,在Manacher的运算过程当中,每个回文串我们都会考虑到

也就是说,我们可以在Manacher的运作过程当中,顺便完成答案的计算,具体操作见下

我们可以画出来这样一张图

# a # b # b # a # a # b # b # a #

0 1 2 3 4 5 6 7 8

我们要更新答案的时候,处在最中间的8号位置,不失一般性,我们处在 i 号位置,要求这个位置是字符#,这个要求的原因很显然

当 i 号位置的回文半径到达4的倍数的时候,说明我们左半边的串的长度是偶数,设当前的回文半径为 r

这时候左半边的串的中心位置就是 i - r/2 ,可以自己手算一下

如果 i - r/2 处的回文半径大于等于 i - r/2 的话,那么显然左半边是回文串,我们找到了一个满足要求子串,就可以ans = max( ans, r )

代码如下:

 1 #include <cstring>
 2 #include <cstdio>
 3 #include <algorithm>
 4 
 5 using namespace std;
 6 const int MAXN = 300010;
 7 
 8 int n;
 9 char str[MAXN], s[MAXN<<1];
10 
11 void input() {
12     scanf( "%s", str ), n = strlen(str);
13     int p = 0;
14     for( int i = 0; i < n; ++i )
15         s[p++] = '#', s[p++] = str[i];
16     s[p++] = '#';
17 }
18 
19 int rd[MAXN<<1]; // 回文半径
20 void manacher() {
21     int mx = 0, p = 0, len = 2*n+1, ans = 0;
22     for( int i = 0; i < len; ++i ) {
23         if( i < mx ) rd[i] = min( rd[2*p-i], mx-i );
24         else rd[i] = 1;
25         while( i+rd[i] < len && i-rd[i] >= 0 && s[i+rd[i]] == s[i-rd[i]] ) {
26             if( s[i] == '#' && rd[i] % 4 == 0 && rd[i-rd[i]/2] >= rd[i]/2 )
27                 ans = max( ans, rd[i] );
28             ++rd[i];
29         }
30         if( i+rd[i] > mx ) mx = i+rd[i], p = i;
31     }
32     printf( "%d\n", ans );
33 }
34 
35 int main() {
36     int T; scanf( "%d", &T );
37     while( T-- ) input(), manacher();
38     return 0;
39 }

 

posted @ 2017-03-10 21:03  mlystdcall  阅读(416)  评论(0编辑  收藏  举报