P4555 最長雙回文串
題目描述
順序和逆序讀起來完全一樣的串叫做回文串。比如acbca
是回文串,而abc
不是(abc
的順序為abc
,逆序為cba
,不相同)。
輸入長度為 n的串 S ,求 S的最長雙回文子串 T ,即可將 T 分為兩部分 X, Y,( ∣X∣,∣Y∣≥1|X|,|Y|≥1∣X∣,∣Y∣≥1 )且 X 和 Y 都是回文串。
輸入輸出格式
輸入格式:一行由小寫英文字母組成的字符串 S 。
輸出格式:一行一個整數,表示最長雙回文子串的長度。
輸入輸出樣例
輸入樣例#1:baacaabbacabb
輸出樣例#1:
12
說明
【樣例說明】
從第二個字符開始的字符串aacaabbacabb
可分為aacaa
與bbacabb
兩部分,且兩者都是回文串。
對於100%的數據, 2≤∣S∣≤105
Solution:
本題$zyys$啊!~
很容易想到$manacher$,於是先打個板子看看,處理出以$i$為中心的最長回文半徑$p[i]$後,就斷思路了。
我首先想到的是,在每次更新$p[i]$後,分別處理出以$i$為中心的半徑$p[i]$內,每個位置為開頭和結尾的最長回文子串長度($manacher$結束後直接枚舉斷點就可以得到答案),但是這樣強行又將復雜度拉到了$O(n^2)$。於是,開始斷線~
後面看看巨佬們的思路,豁然**,我是真的蠢啊~
其實,將我開始的思路修改一下即可:
我們維護最長回文半徑$p[i]$的同時,再分別維護兩個東西,以$i$為結尾的最長回文子串的長度$ll[i]$,和以$i$為開頭的最長回文子串的長度$rr[i]$。
那麽很顯然,因為以$i$為中心的最長回文子串長度為$p[i]-1$,所以每次更新$p[i]$後,我們只需處理出當前這個回文子串的左右邊界(中間的每個點的$ll[i],rr[i]$可以在$manacher$結束後$O(n)$處理出),則$ll[i+p[i]-1]=max(ll[i+p[i]-1],p[i]-1)$(更新以$i+p[i]-1$為結尾的最長回文長度),同理$rr[i-p[i]+1]=max(rr[i-p[i]+1],p[i]-1)$。
跑完$manacher$後,我們$O(n)$遞推出每個$#$為斷點的$ll[i]$和$rr[i]$,其中$rr[i]$因為是$i$結尾的回文長度,所以直接順推,每往後移一位,最長回文子串長度$-2$,於是$rr[i]=max(rr[i],rr[i-2]-2)$($i-2$是上一個$#$位置),同理$ll[i]$直接逆推,類似地$ll[i]=max(ll[i],ll[i+2]-2)$。
最後枚舉每個$#$為斷點,更新$ans$就$OK$了。
代碼:
1 #include<bits/stdc++.h> 2 #define For(i,a,b,c) for(int (i)=(a);(i)<=(b);(i)+=(c)) 3 #define Bor(i,a,b,c) for(int (i)=(b);(i)>=(a);(i)-=(c)) 4 #define Min(a,b) ((a)>(b)?(b):(a)) 5 #define Max(a,b) ((a)>(b)?(a):(b)) 6 using namespace std; 7 const int N=200050; 8 int p[N],ll[N],ans,rr[N],mx,id,cnt; 9 char s[N],t[N]; 10 int main(){ 11 scanf("%s",t); 12 int len=strlen(t); 13 s[++cnt]=‘$‘,s[++cnt]=‘#‘; 14 For(i,0,len-1,1)s[++cnt]=t[i],s[++cnt]=‘#‘; 15 s[++cnt]=‘\0‘; 16 For(i,1,cnt,1){ 17 if(i<mx)p[i]=Min(p[id*2-i],mx-i); 18 else p[i]=1; 19 while(s[i-p[i]]==s[i+p[i]])p[i]++; 20 if(mx<i+p[i])id=i,mx=i+p[i]; 21 ll[i+p[i]-1]=Max(ll[i+p[i]-1],p[i]-1); 22 rr[i-p[i]+1]=Max(rr[i-p[i]+1],p[i]-1); 23 } 24 For(i,2,cnt,2)rr[i]=Max(rr[i],rr[i-2]-2); 25 Bor(i,2,cnt,2)ll[i]=Max(ll[i],ll[i+2]-2); 26 For(i,2,cnt,2)if(rr[i]&&ll[i])ans=Max(ans,ll[i]+rr[i]); 27 cout<<ans; 28 return 0; 29 }
P4555 最長雙回文串