manacher(馬拉車)算法詳解+例題一道【bzoj3790】【神奇項鏈】


這里寫圖片描述
[pixiv] https://www.pixiv.net/member_illust.php?mode=medium&illust_id=39091399
(CSDN好像有bug,不知道為什么存的草稿覆蓋了之前的博客>.<,以后再也不存線上草稿了)

昨天學了馬拉車(manacher)算法,今天做了一道例題。雖然並不難,但還是寫一寫博客,即為民服務,又加深自己的理解。

manacher是高效處理回文串的算法,不過因為只限求回文串,所以適用范圍就窄了,但是它仍然有用,所以還是要學。

首先是一個小小的處理技巧。因為回文串可能是奇數可能是偶數,偶數的時候就沒有中心字符了,沒有著腳點可惱火了。所以我們考慮用沒有出現過的字符“#”來表示原串的間隔。eg。ababa->#a#b#a#b#a#。為了避免溢出,訪問到不存在的點(’\0’=’\0’),我們再在首尾加上沒有出現過的不同字符,eg’+’,’-‘。顯然,現在對新串的每一個字符找以此字符為中心的字符串即可。

manacher的思想就是從左到右求出以每個位置為中心的最長字符串(由於回文串很明顯的包含和對稱性質,以同一位置為中心的字符串自然是包含在最長回文串里),利用回文串的對稱性質,對每一個位置利用之前的信息來快速得到答案。近似於掃一遍,所以均攤o(n)(之后詳細證明)

那么之前的信息是什么呢?我們記向右延伸最遠的回文串(有多個的話,選中心點最靠左的),令其右端點為mx,中心點的下標為id。我們需要處理數組pal[i],表示以i為中心的回文串的右端到i的長度。

下面就是怎么實現的問題了。
對於一個位置i,有三種情況
1、i<=mx,我們可以由對稱性得出其關於id的對稱點的pal[]
這里寫圖片描述
此時易得pal[i]=pal[2*id-i];
2、同樣的情況,i<=mx,但是其對應點的范圍超出了id的范圍。
這里寫圖片描述
此時可得pal[i]>=mx-i+1;
由對稱性可得,在id范圍內的部分i可由2*id-i推來,但外面的部分必定與另一邊不同(想想),暴力擴充即可,將mx更新。暴力判斷pal[i]能到多少,每次判斷成功都等價於mx++,失敗則結束。由於mx是單增的,於是判斷成功的次數不超過串長次,均攤o(n)
3、i>mx。處理方法與2相同。

放個代碼更好理解

void manacher(){
    int mx=0,id;
    for(int i=1;i<=m;i++){
        if(mx>=i) pal[i]=min(mx-i+1,pal[2*id-i]);
        else pal[i]=1;
        while(s[i-pal[i]]==s[i+pal[i]]) pal[i]++;
        if(i+pal[[i]-1>mx)
            mx=i+pal[i]-1,id=i;
        }
    }
}

由代碼可以也可以看出,mx是單增的,均攤o(n)

此外,manacher算法還引出了一個——

極重要的性質

這里寫圖片描述
再來看這張圖,我們發現,如果mx不更新,就不會出現本質不同的回文子串,因為前面已經出現過了;而每擴展一次mx,最多新出現一個本質不同的回文子串。
於是得到性質:#一個字符串最多只有n個本質不同的回文子串#。這個性質很重要,有些題會用到,需要這個性質去分析。

基礎講完了,終於可以放題了:D

Description
母親節就要到了,小 H 准備送給她一個特殊的項鏈。這個項鏈可以看作一個用小寫字
母組成的字符串,每個小寫字母表示一種顏色。為了制作這個項鏈,小 H 購買了兩個機器。第一個機器可以生成所有形式的回文串,第二個機器可以把兩個回文串連接起來,而且第二個機器還有一個特殊的性質:假如一個字符串的后綴和一個字符串的前綴是完全相同的,那么可以將這個重復部分重疊。例如:aba和aca連接起來,可以生成串abaaca或 abaca。現在給出目標項鏈的樣式,詢問你需要使用第二個機器多少次才能生成這個特殊的項鏈。
Input
輸入數據有多行,每行一個字符串,表示目標項鏈的樣式。
Output
多行,每行一個答案表示最少需要使用第二個機器的次數。
Sample Input
abcdcba
abacada
abcdef
Sample Output
0
2
5
HINT
每個測試數據,輸入不超過 5行
每行的字符串長度小於等於 50000

先用manacher跑出pal[ ]數組,因為題目說可以重合,於是就轉換成了經典的區間覆蓋問題,貪心即可。

代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=50000+5;

int len,pal[N*2];
char s[N],a[N*2];
struct Node{
    int le,ri;
}qu[N*2];

bool cmp(Node a,Node b){
    return a.le<b.le;
}
void insert(){
    memset(a,0,sizeof(a));
    int lena=-1;
    a[++lena]='+';
    for(int i=0;i<len;i++){
        a[++lena]='#';
        a[++lena]=s[i];
    }
    a[++lena]='#';
    a[++lena]='-';
    len=lena-1;
}
void manacher(){
    int mx=0,id;
    for(int i=1;i<=len;i++){
        if(mx>=i) pal[i]=min(mx-i+1,pal[2*id-i]);
        else pal[i]=1;
        while(a[i-pal[i]]==a[i+pal[i]]) ++pal[i];
        if(mx<i+pal[i]-1)
            mx=i+pal[i]-1,id=i;
    }
}
int fugai(){
    int ans=0,far=1;
    int i=1;
    for(i=1;qu[i].le<=1;i++)
        if(qu[i].ri>qu[far].ri) far=i;
    while(i<=len){
        ans++;
        int tmp=far;
        for(;qu[i].le<=qu[far].ri;i++)
            if(qu[i].ri>qu[tmp].ri) tmp=i; 
        far=tmp;
    }
    return ans;
}
int main(){
    while(scanf("%s",s)!=EOF){
        len=strlen(s);
        insert();
        manacher();
        memset(qu,0,sizeof(qu));
        for(int i=1;i<=len;i++)
            qu[i].le=i-pal[i]+1,qu[i].ri=i+pal[i]-1;
        sort(qu+1,qu+len+1,cmp);
        printf("%d\n",fugai()-1);
    }
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM