[Manacher]最长回文子串

很久没有写博客了
啪啪啪
写一些东西吧

最长回文子串怎么求呢
首先我们得知道什么是子串,给你一个长长的串,里面任意连续的一段就是它的子串,当然一个字符也是子串
接着什么是回文串呢 不好描述 但是看例子很容易懂:aba 121 1221 1
然后我们有一种很显然的寻找方法 当然是枚举中点 然后尽可能的往外扩大回文串的长度 这种算法花费的时间是N(字符串长度)*Mk(可扩展长度)Mk<N

当然这样的时间并不是很让人满意
于是Manacher这个人发明了一种新算法
他是这样想的
假如我先前已经找到的回文串已经是很大了,比如已经覆盖了整个字符串那么长
那我找到它以后 就不要找了啊 根据回文串左右对称的特性
我们可以利用之前找到的可扩展的右端,以及扩展起始点
如果当前查找的被扩展最右端覆盖,那么当前查找的起始点可扩展的最小值就是以扩展起始点为对称中心相对称的那个点的扩展值
如果扩展到了边界,就可以继续扩展下去直到不能扩展,啪啪啪就结束了

非常精彩 但是有个问题 中点可能是一个字符 也可以是两个字符
分情况讨论?费劲
有一种很机智的方法 往字符串每个字符间掺同一个不属于这个字符串的字符然后前后各掺一个
显然假如字符串有n个 新字符串必为2*n+1 也就是只需要讨论一种情况了
至于到底要求长度还是求字符 就在求的时候开个最大值记录的存一下就行了

这个算法的时间花费是4*N(字符串长度)(建一遍,找一遍,长度因为扩大了一倍所以是2N)

下面是个打得很烂的模板

//Manacher算法
string s;
char inser='&';
char str[301000];
int len[301000];
void cvt(int n){
    str[0]=inser;
    for(int i=1;i<=(n<<1);i+=2){
        str[i]=s[i>>1];
        str[i+1]=inser;//cout<<str[i]<<" "<<str[i+1]<<" ";
    }
}
void getLen(int l){
    int p0=0,p=0;len[0]=1;
    int j;
    for(int i=1;i<=l;i++){
        if(i<=p){
            j=(p0<<1)-i;
            if(len[j]<(p-i))len[i]=len[j];
            else len[i]=p-i;
//          len[i]=min(len[2*p0-i],p-i);
        }
        else
            len[i]=1;
        int cl=i-len[i],cr=i+len[i],temp=len[i];
        while(cl>=0&&cr<=l&&str[cl]==str[cr]){
        //  if(str[cr]!=inser&&str[cr]>str[cr-2])break;
            temp++;
            cl--;cr++;
        }
        len[i]=temp;
        if((i+len[i]-1)>p){
            p=i+len[i]-1;
            p0=i;
        }
    }
}
int main(){
    while(cin>>s){
        int n;
        n=s.size();
        cvt(n);
        getLen(n<<1);
        int ans=0;
        for(int i=0;i<=(n<<1);i++)
        ans=max(ans,len[i]);
        cout<<ans-1<<endl;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/passguan/p/9501192.html