luooj[USACO 1.3.3]Calf Flac

傳送門

manacher算法

用來找字符串中的最長迴文子串的algorihtm

   Manacher算法可以將長度爲奇數和偶數的迴文串一起考慮:
   在原字符串的相鄰字符串之間插入一個分隔符,字符串的首尾也要分別添加,
   注意分隔符必須是原字符串中沒有出現過的
原字符串s   a   b   a   b   c
轉換後字符串str   #   a   #   b   #   a   #   b   #   c   #

一、Len數組的簡單介紹

    Manacher算法中用到一個非常重要的輔助數組
    Len[i]表示以str[i]爲中心的最長迴文子串的最右端到str[i]位置的長度,
    比如以str[i]爲中心的最長迴文串是str[l,r],那麼Len[i]=r-i+1
轉換後的字符串str  #   a   #   b   #   a   #   b   #   c   #
Len 1   2   1   4   1   4   1   2   1   2   1
Len[i]數組有一個性質,Len[i]-1就等於該回文串在原串s中的長度
證明:在轉換後的字符串str中,所有的迴文串的長度都是奇數,
那麼對於以str[i]爲中心的最長迴文串的長度爲2*Len[i]-1,
其中又有Len[i]個分隔符,所以在原字符串中的長度就是Len[i]-1,
那麼剩下的工作就是求Len數組

二、Len數組的計算

從左往右開始計算,假設0<=j<=i,那麼在計算Len[i]時,Len[j]已經計算過了,
設mx爲之前計算過的最長迴文串的右端點,
id爲取得這個端點值得位置(那麼Len[id]=mx-id+1)

第一種情況:i < mx
找到i相對於id的對稱位置,設爲j,再次分爲兩種情況:
1、Len[j] < mx-i

    mx的對稱點爲2*id-mx,ij所包含的範圍是2*Len[j]-1
    那麼說明以j爲中心的迴文串一定在以id爲中心的迴文串內部,
    且ij關於id對稱,由迴文串的定義可知,一個迴文串反過來仍是迴文串,
    所以以i爲中心的迴文串長度至少和以i爲中心的迴文串長度相等,
    即Len[i]>=Len[j].因爲Len[j]<mx-i所以i+Len[j]<mx,
    由對稱性可知Len[i]=Len[j].
  2、Len[j] > mx-i(此處不能等)
由對稱性說明以i爲中心的迴文串可能延伸到mx之外,
而大於mx的部分我們還沒有進行匹配,
所以要從mx+1位置開始一個一個匹配直到失配,
從而更新mx和對應的id以及Len[i]

第二種情況,i>mx或Len[j]=mx-i

  1.如果說等於的時候會可能Len[i]仍有可能會增大
  2,如果imx還大,說明對於中點爲i的迴文串一點都沒匹配,
  這個兩種情況只能一個個匹配,匹配完成後更新mx的位置和對應的idLen[i].

具體實現見代碼

#include<bits/stdc++.h>
#define N 500000
using namespace std;
char s[N*2],str[N*2];
int Len[N*2],len;
void getstr(){
    int k=0;
    str[k++]='$';
    for(int i=0;i<len;i++)
        str[k++]='#',
        str[k++]=s[i];
    str[k++]='#';
    len=k;
}
void Manacher(){
    getstr();
    int mx=0,id;
    for(int i=1;i<len;i++){
        if(mx>i) Len[i]=min(Len[2*id-i],mx-i);
        else Len[i]=1;
        while(str[i+Len[i]]==str[i-Len[i]]) 
            Len[i]++;
        if(Len[i]+i>mx) mx=Len[i]+i,id=i;
    }
}
int main(){
    while(~scanf("%s",&s)){
        len=strlen(s);
        Manacher();
        int ans=1;
        for(int i=1;i<len;i++) ans=max(ans,Len[i]);
        printf("%d\n",ans-1);
    }
    return 0;
}

講完了模板現在分析一下這道題目,這題是一題模板題;
首先讀入有點麻煩爲此wa了一次,讀入要注意細節,
第二就是用到了那個映射,因爲原串和後來的串是有區別的;
詳見代碼

include<bits/stdc++.h>
#define N 200005
using namespace std;
char s[N*2],str[N*2],ss[N*2];
int Len[N*2],len,n,idd,mp[N*2];
void getstr(){
    int k=0;
    str[k++]='$';
    for(int i=0;i<len;i++){
        str[k++]='#';
        str[k++]=ss[i];
    }
    str[k++]='#';
    len=k;
}
void Manacher(){
    getstr();
    int mx=0,id;
    for(int i=1;i<len;i++){
        if(mx>i) Len[i]=min(Len[2*id-i],mx-i);
        else Len[i]=1;
        while(str[i+Len[i]]==str[i-Len[i]]) 
            Len[i]++;
        if(Len[i]+i>mx) mx=Len[i]+i,id=i;
    }
}
void read(){
    char c;
    while((c=getchar())!=EOF){
            s[++n]=c;
            if ((c>='A'&&c<='Z')||(c>='a'&&c<='z')){
                if(c>='A'&&c<='Z') ss[len++]=c+32;
                if(c>='a'&&c<='z') ss[len++]=c;
                mp[len]=n;
            }
    }
}
int main(){
    read();
    Manacher();
    int ans=1;
    for(int i=1;i<len;i++){
        if(ans<Len[i]){
            ans=Len[i];
            idd=i;
        }
    }
    ans--;
    printf("%d\n",ans);
    int sx=(idd-ans)/2;  
    int sy=(idd+ans)/2;
    for(int i=mp[sx+1];i<=mp[sy];i++)
        printf("%c",s[i]);
    return 0;
}
發佈了59 篇原創文章 · 獲贊 8 · 訪問量 7621
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章