1. 程式人生 > >Manacher算法模板

Manacher算法模板

其中 意義 printf utc problem span int 重復 mes

傳送門

Manacher算法是解決回文串長度計算的利器。

優秀的算法大多起源於暴力的思想……我們一步一步來看。

首先思考最暴力的情況怎麽匹配?枚舉所有區間然後判斷是不是回文串,時間復雜度O(n^3).

之後我們考慮優化一下。因為回文串的左右是相同的,所以我們不妨可以枚舉回文串的中點,之後向兩邊依次拓展,直到不能再拓展為止,這樣枚舉是O(n),每次掃一遍也是O(n)的,時間復雜度變成了O(n^2)的。

然後想想上面的還能怎麽優化。

1.首先一個問題是我們要考慮字符串長度的奇偶性,如果是奇數的話其中心能選到一個字符,不過如果是偶數的話那麽就會選到兩個字符中間的位置。我們解決的方法是把每兩個字符中間加上一個原串中沒有出現過的字符,比如‘#’。註意一定是要加相同的,因為這樣是不會影響字符串匹配的。之後我們就把所有的字符串改成了奇數長度的。

2.在第二種算法中有多種情況被重復計算。

這裏就要說到算法的主體了。我們建立一個輔助數組p[i]表示第i個位置能拓展出的最大的回文子串的長度的半徑。這樣的話一個位置的最長回文串長度就是p[i]-1。(因為裏面有好多被填充過的字符)再設一個變量mx表示當前回文串能拓展到的最右邊的位置,mid表示當前回文串的中心。

然後利用回文串左右兩端相同的性質,如果當前枚舉的點i在mx的左邊而且在mid的右邊,設i關於mid的對稱點是j,那麽我們就可以知道p[i] >= p[j](因為左右子串是相同的)其中j的計算方法是mid<<1 - i.之後我們繼續向兩邊進行拓展即可(就像算法2一樣)。

如果當前枚舉到的點在mx的右邊,這時候因為mx右邊所有的點還沒有被匹配,我們無法保證兩邊的字符串是相同的,這時就只能先設置p[i]=1,之後暴力匹配。

這樣的話我們就做完了……是不是很簡單?

這個算法的時間復雜度是O(n)的,所以是解決在長字符串中最長回文串長度的有力武器。至於它為什麽是O(n)的,因為其實你的匹配是與mx有關的,mx是保證單調遞增的。具體說一點,就是我們首先更新這個點能匹配到的最長回文串半徑長度,之後我們再向後匹配的時候,如果你是通過翻轉轉移過來,它下一位匹配應該會失敗(否則你一開始的長度會更長),真正有意義的匹配都是從mx開始的,只要從mx開始你才有可能獲得一個正確的匹配。因為mx保證單調遞增,所以算法的時間復雜度是O(n)的。

算法的代碼非常簡潔。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)
using namespace std;
typedef long long ll;
const int M = 100005;
const int N = 10000005;
 
int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
    if(ch == -) op = -1;
    ch = getchar();
    }
    while(ch >=0 && ch <= 9)
    {
    ans *= 10;
    ans += ch - 0;
    ch = getchar();
    }
    return ans * op;
}

char s[N<<2],c[N<<1];
int len,p[N<<2];

int change()//將字符串改造
{
    int l = strlen(c),j = 2;
    s[0] = !,s[1] = #;//註意最開始和最末尾是要換成另一種字符的
    rep(i,0,l-1) s[j++] = c[i],s[j++] = #;
    s[j] = &;
    return j;
}

int manacher()
{
    int len = change(),mx = 1,mid = 1,ans = 1;
    rep(i,1,len-1)
    {
    if(i < mx) p[i] = min(mx - i,p[(mid<<1)-i]);//更新這個點的最大回文串匹配長度
    else p[i] = 1;
    while(s[i-p[i]] == s[i+p[i]]) p[i]++;//如果兩邊能匹配上,那麽長度++
    if(mx < i + p[i]) mid = i,mx = i + p[i];//如果mx已經在當前的匹配中心和匹配半徑之和的左面,那麽我們更新mx和mid
    ans = max(ans,p[i]-1);//更新答案,每一位的答案就是p[i]-1
    }
    return ans;
}

int main()
{
    scanf("%s",c);
    printf("%d\n",manacher());
    return 0;
}

Manacher算法模板