跟风Manacher算法整理

这是上上周天机房一位神仙讲的,\(gu\)了这么久才来整理\(w\),神仙讲的基本思路已经全都忘记了,幸好的是神仙写了\(blog\),吹爆原博浅谈\(Manacher\)算法,以及原博神仙\(ych\)!
再吹一波\(ych\)

太巨了!

\(Manacher\)是一种\(O(n)\)求回文字符子串的算法。(然后迷惑的记得当时问神仙\(ych\)一个sha diao问题:子串是连续的嘛?显然这里的回文子串是连续的;

\(Solution:\)

对于一串字符串,对于其中的每一个字符我们都维护一个\(R[i]\)表示这个字符串的最长回文半径,但是这个时候出现了\(bug\)

\(ykyyky\)

\(ykykyky\)

对于前一个子串,是偶数回文子串,而后一个回文子串是奇数回文子串。这个时候我们该怎么表示它们回文半径的差别?\(3\)\(3.5\)?✘ 这个时候我们可以在每个字符串之间加‘\(\#’\)

\(\#y\#k\#y\#y\#k\#y\#\)

\(\#y\#k\#y\#k\#y\#k\#y\#\)

于是这样它们的回文半径就唯一确定了;

看处理:\(R[i]\)表示最长回文半径,当我们求得每个位置的\(R[i]\),当加了\('\#'\)之后,\(R[i]_{max}-1\)就是我们要求的最长回文串长度(感性 举例李姐

怎么处理?

\(R[i]\)

设前\(i-1\)个数中的回文串的右端点的最大值为\(r\),取得最大右端点的数为\(mid\)。显然\(r=mid+R[mid]\)

\(\mathfrak{A}.\)\(i\leq r\)

计算\(i\)关于\(mid\)的对称点\(j=mid*2-i\),

\(\mathfrak{a}.\)\(j-R[j]>mid-R[mid]\),即\(i\)的对称点的回文串的范围包含在\(mid\)对应点的回文串范围,那么\(i\)的回文串和\(j\)的回文串一定是对称分布的(因为\(i、j\)关于\(mid\)对称并且在\(mid\)的回文半径内),则\(R[i]=R[j]\)

\(\mathfrak{b}.\) \(j-R[j]\leq mid-R[mid]\),则此时关于\(i、j\)关于\(mid\)对称分布并且在\(mid\)回文半径内的一定是对称的,但是在回文半径之外是否对称我们不清楚,因此我们用最简单粗暴的办法:暴力拓展;

\(\mathfrak{B}.i>r\)

于是暴力拓展√

在每次完成以上三项后,尝试更新\(r、mid\)

if(r<i+R[i]) {
    r=i+R[i]-1;
    mid=i;
}

然后复杂度不会证,(一定是我太菜了.

\(Code:\)

码量不是很大,注意字符串头尾都要插入一个\('\#'\)

#include<bits/stdc++.h>

using namespace std;

char s[22000703];
int R[22000703],len;

void read() {
    char ch=getchar();
    s[0]='~';s[++len]='#';
    while(ch>'z'||ch<'a') ch=getchar();
    while(ch>='a'&&ch<='z') s[++len]=ch,s[++len]='#',ch=getchar();
}

int main () {
    read();
    int r=0,mid=0,ans=0;
    for(int i=1;i<=len;i++) {
        if(i<=r) R[i]=min(R[2*mid-i],r-i+1);
        while(s[i-R[i]]==s[i+R[i]]&&s[i-R[i]]!='~') ++R[i];
        if(r<i+R[i]) {
            r=i+R[i]-1;
            mid=i;
        }
        ans=max(ans,R[i]);
    }
    printf("%d",ans-1);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zhuier-xquan/p/12092722.html