【题解】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 }