题意:最长回文子串长度、
题解:Manacher裸题、
Manacher:
本质:重复利用之前的信息来快速更新新的状态、
思想:
首先我们维护一个id指针表示可以用id指针更新i的【状态】。
【状态】:p[i]表示以i为中心的最长回文半径。
(一般i+p[i]是第一个'超出回文',即拓展到这里不成回文,下文也按照这个习惯来写)
有时回文子串长度是偶数,就会以一个不存在的'空'为中心,所以往往在两个字符间加一个分割字符,比如ab这个串就往往会被改成~a~b~,然后Manacher中有一个延展的步骤需要比较当前回文的两边,进行延展,为了避免边界条件,所以我们往往会在前面再加一个字符,变成¥~a~b~。
分析转移:
我们可以发现对于一个id指针,i会在id的另一侧有一个对称点j,
Ⅰ. 如果以j为中心的回文子串没有超出以id为中心的回文子串,那么以i为中心的回文子串显然与以j为中心的回文子串相同。
Ⅱ. 否则,因为超出部分不能保证相等,所以回文子串需要被截到以id为中心的回文子串的边界来更新p[i]。
那么p[i]就可以有一个初始值p[i]=min(p[id*2-i],mx-i);
然后我们只是给以i为中心的回文子串确定了一个最小回文半径,它并不一定是p[i]的真实值。
所以我们再暴力往两边拓展。
那么我们如何确定一个比较好的id呢?
可以发现,我们需要p[i]在暴力拓展前的值尽量大,那么我们就需要以id为中心的回文子串能容纳的以i为中心的回文子串的限度尽量大,即id+p[id]尽量大。
于是我们再定义一个mx表示id+p[id]来判定当前最优的id。
时间复杂度分析:
首先我们发现manacher函数中有一个for循环,其中只有一个while可能存在时间复杂度的拓宽。
分析:while循环的作用——>暴力拓展p[i],而这对应上文的Ⅱ. ,可以分析得到如果p[i]增加,那么k+p[k](k∈[1,i])的最大值,即mx肯定就等于 i+p[i],也即id会被更新成i。
而mx最大不会超过len,即进入while循环并p[i]++的次数不会超过len。
而len约为原len<<1 ,所以可以证明manacher的时间复杂度是O(n) 的。。。
贴代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 1001000 using namespace std; char ts[N],s[N<<1]; int p[N<<1]; int manacher() { int len=strlen(ts),i; s[0]='&',s[1]='^'; for(i=1;i<=len;i++)s[i<<1]=ts[i-1],s[i<<1|1]='^'; len++,len<<=1; int ret=0,mx=0,id=0; for(i=1;i<len;i++) { if(mx>i)p[i]=min(p[id*2-i],mx-i); else p[i]=1; while(s[i-p[i]]==s[i+p[i]])p[i]++; if(mx<p[i]+i)mx=p[i]+i,id=i; ret=max(ret,p[i]); } return ret-1; } int main() { // freopen("test.in","r",stdin); // freopen("my.out","w",stdout); int i,j,k; int id=0; while(scanf("%s",ts),ts[0]!='E'&&ts[0]!='O'&&ts[0]!='F')printf("Case %d: %d\n",++id,manacher()); return 0; }