马拉车——P4555 [国家集训队] 最长双回文串
题意:
双回文串A是指一个可以被拆分成两个部分(B和C)的字符串 A=B+C, 且B和C都是回文串的串, A自己本身可以不是回文串.
题解:
首先想到manacher 在维护p[i]的同时 维护l[i],r[i]
l[i]:以i开头的最长回文子串的长度
r[i]:以i结尾的最长回文子串的长度
因为以i为中心的最长回文子串长度为p[i]-1,所以每次更新后我们只要处理当前这个回文子串的左右边界(中间的每个点的l[i]和r[i]可以在manacher结束后求出)
所以 l [ i - p [ i ] + 1 ] = max ( l [ i - p [ i ] + 1 ] , p [ i ] - 1 )
r [ i + p [ i ] - 1 ] = max ( r [ i + p [ i ] - 1 ] , p [ i ] - 1)
跑完manacher后,我们O(n)递推出每个‘#’断点的l[i]和r[i]
l[i]顺推 每往后移动一位,最长回文子串长度-2,所以 l [ i ] = max ( l [ i ] , l [ i - 2 ] - 2 ) (i-2是上一个 '#' 的位置) 同理 r[i] 逆推
最后枚举每个'#'为断点,更新ans
注意:判断当 l[i] 和 r[i] 都不为0的时候才更新,要保证左边是回文串右边也是回文串
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+5; char s[maxn],str[maxn]; int l1,l2,p[maxn],ans; int l[maxn],r[maxn]; void init() { str[0]='$'; str[1]='#'; for(int i=0; i<l1; i++) { str[i*2+2]=s[i]; str[i*2+3]='#'; } l2=l1*2+2; str[l2]='*'; } void manacher() { int id=0,mx=0,ans=0; for(int i=1; i<l2; i++) { if(mx>i)p[i]=min(p[2*id-i],mx-i); else p[i]=1; for(; str[i+p[i]]==str[i-p[i]]; p[i]++); if(p[i]+i>mx) { mx=p[i]+i; id=i; } l[i-p[i]+1]=max(l[i-p[i]+1],p[i]-1); r[i+p[i]-1]=max(r[i+p[i]-1],p[i]-1); } } int main() { scanf("%s",s); l1=strlen(s); init(); manacher(); for(int i=1;i<l2;i+=2)l[i]=max(l[i],l[i-2]-2);///只需要处理 # 位置的l,r即可 for(int i=l2-1;i>=1;i-=2)r[i]=max(r[i],r[i+2]-2); for(int i=1;i<l2;i+=2)if(l[i] && r[i])ans=max(ans,l[i]+r[i]); printf("%d\n",ans); return 0; }