BZOJ2565:最长双回文串——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=2565
题目大意:
顺序和逆序读起来完全一样的串叫做回文串。比如acbca是回文串,而abc不是(abc的顺序为“abc”,逆序为“cba”,不相同)。
输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(|X|,|Y|≥1)且X和Y都是回文串。
输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(|X|,|Y|≥1)且X和Y都是回文串。
——————————————————————
看到回文串长度最大,先敲一个manacher算法。
看到回文串长度最大,先敲一个manacher算法。
然后思考如何更新每一个字符以其为起点和终点的最长回文串。
显然都跑一遍肯定是不行的(尝试过TLE…)
那么我们考虑一个很简单的事实,对于一个在某几个回文串的字符,显然当它属于mx靠前的回文串时以它为终点的回文串长度最长。
那么我们就有了一种近似O(n)的算法,通过记录我们最新一次更新的字符位置now,然后搜索每一个回文串,当回文串右端点超过了now的时候,就对now和右端点之间的字符更新。
同理处理另一种情况,然后枚举取最大值即可。
PS:我们已知回文串的中点i,怎么求在回文串内的j以其为终点的回文串长度。
我们有结论为j-i+1,简易证明:
我们知道我们最开始的时候是含有“#”插入的字符串处理,此时回文串的长度为2*(j-i)+1.
而实际上其中包含了“#”有j-i个(就是长度/2向下取整)。
相减得到j-i+1。
#include<cstdio> #include<cstring> #include<algorithm> #define N 100010 using namespace std; int left[2*N],right[2*N],mx,id,p[2*N]; char s[2*N]; int main(){ scanf("%s",s+1); int l=strlen(s+1); s[0]='@'; for(int i=l;i>=1;i--)s[i*2]=s[i]; for(int i=1;i<=2*l+1;i+=2)s[i]='#'; s[2*l+2]='?'; l=2*l+1; for(int i=1;i<=l;i++){ if(mx>i)p[i]=min(p[2*id-i],mx-i); else p[i]=1; while(s[i-p[i]]==s[i+p[i]])p[i]++; if(i+p[i]>mx){ mx=i+p[i]; id=i; } } int now=0; for(int i=1;i<=l;i++){ if(i+p[i]-1>now){ for(int j=now+1;j<=i+p[i];j++){ left[j]=j-i+1; } now=i+p[i]-1; } } now=l+1; for(int i=l;i>=1;i--){ if(i-p[i]+1<now){ for(int j=now-1;j>=i-p[i];j--){ right[j]=i-j+1; } now=i-p[i]+1; } } int ans=0; for(int i=2;i<=l;i+=2){ ans=max(ans,left[i]+right[i+2]); } printf("%d\n",ans); return 0; }