仔细分析,逐个击破
思路:先分析双回文串的性质,即一个 长度为4的倍数(条件1),左右两部分相等(条件2) 且 左右两部分都为长度为偶数的回文串(条件3) 的回文串(条件4)
可以看出,当满足条件2,3,4时,条件1自动满足(这个特别明显,看不出来可以自己画一下),所以可以不用特别考虑条件1
又可以看出,当满足条件2,4时,条件3自动满足,当满足条件3,4时,条件2自动满足(这个也很明显),所以条件2,3只需要考虑其中一个即可
所以,问题从需要考虑4个条件变成了考虑2个条件
遇到求回文串的题目,看看一些基本算法好像无法解决,肯定会先往manacher和PAM方面想的
先想想manacher算法应该如何解决这个问题
满足条件4?简单,manacher秒了
问题来到如何满足条件2或3的其中一个
我们看看manacher算法的内部(这里采用分开计算d1,d2,即以i为中心的长度为奇数/偶数的回文串个数,且由于这题只需要长度为偶数,所以只需要求d2)
void manacher(){ for(int i=0,l=0,r=-1;i<n;i++){ int k=(i>r) ? 0 : min(d2[l+r-i+1],r-i+1); while(0<=i-k-1 && i+k<n && s[i-k-1]==s[i+k]){ k++; } d2[i]=k--; if(i+k>r){ l=i-k-1; r=i+k; } } return ; }观察此处
while(0<=i-k-1 && i+k<n && s[i-k-1]==s[i+k]){ k++; //左右两个字符相同,以i为中心的长度为偶数的回文串长度+1 }我们知道,回文串原本就有左右对称的性质,如果考虑用代码实现条件2,需要求解左右相等,这看上去有点麻烦,因为看上去需要再开一个for循环,时间复杂度会有问题,我们期望能够O(1)实现这种双倍回文串的判定
考虑用代码实现条件3,需要求解左右两部分都为偶数的回文串
看看代码,当manacher跑到下标为i时,当回文半径为k时,说明左右两边的长度都为k
那么,当k为偶数时,左右两部分都为偶数,条件3的前半部分满足了
现在考虑后半部分,即左右都为回文
由于双倍回文串左右对称的性质,所以可以只考虑一边回文的情况,如左边回文
再看看代码,当manacher跑到下标为i时,说明之前的下标都已经计算完毕了
那么,既然此时回文半径为k,说明左侧回文的偶数中心j为i-(k/2)
找到这个偶数中心,可以使用其d2,看d2[j]是否>=( k / 2 ),是的话说明左侧是回文的,右侧由于对称也是回文的,条件3成立,做完了
看看时间复杂度,manacher为O(n),里面的判定字符串为O(1)
核心代码如下
while(0<=i-k-1 && i+k<n && s[i-k-1]==s[i+k]){ k++; if(!(k&1)){ //双倍回文的左边的串长度为偶数才考虑 int j=i-(k>>1); //找到双倍回文的左边的串的中间部分 if(j>=0){ if(d2[j]>=(k>>1)){ //左边为回文串时,无需判断右边,因为k能够到这里,说明右边和左边对称,一定回文 maxn=max(maxn,k<<1); } } } }主代码如下
int d2[N]; //d2:以i为中心的长度为偶数的回文串个数 char s[N]; int n; int maxn; void manacher(){ for(int i=0,l=0,r=-1;i<n;i++){ int k=(i>r) ? 0 : min(d2[l+r-i+1],r-i+1); while(0<=i-k-1 && i+k<n && s[i-k-1]==s[i+k]){ k++; if(!(k&1)){ //双倍回文的左边的串长度为偶数才考虑 int j=i-(k>>1); //找到双倍回文的左边的串的中间部分 if(j>=0){ if(d2[j]>=(k>>1)){ //左边为回文串时,无需判断右边,因为k能够到这里,说明右边和左边对称,一定回文 maxn=max(maxn,k<<1); } } } } d2[i]=k--; if(i+k>r){ l=i-k-1; r=i+k; } } return ; } void InfiniteLight() { read(n); read(s); manacher(); println(maxn); return ; }