Manacher算法學習筆記

前言ios

Manacher(也叫馬拉車)是一種用於在線性時間內找出字符串中最長迴文子串的算法算法

算法spa

通常的查找回文串的算法是枚舉中心,而後往兩側拓展,看最多拓展出多遠。最壞狀況下$O(n^2)$code

然而Manacher可以充分利用迴文的性質blog

首先,迴文分爲奇迴文(好比$aba$)和偶迴文(好比$abba$),若是分開來討論會很麻煩。字符串

因而咱們在原串的首尾以及每兩個字符之間各插入一個原串中沒有出現過的字符。好比$abbbac$,變成$\%a\%b\%b\%b\%a\%c\%$get

那麼這樣的話,上面的$aba$變成$\%a\%b\%a\%$,$abba$變成$\%a\%b\%b\%a\%$,都變成了奇迴文string

咱們定義$p[i]$爲以$i$爲中心的迴文串的最長半徑,好比串$\%a\%b\%a\%$,其中$b$爲第四個字符,則$p[4]=4$(由於以他爲中心的最長迴文串是$\%a\%b\%a\%$,而這個迴文串的半徑爲4)io

那麼咱們原串中以這個位置爲中心的最長迴文串長度就是$p[i]-1$(一個要去掉$\%$,而後半徑的話要防止中心被多算一次)模板

那麼如今的問題就是怎麼快速求出$p[i]$了

咱們假設$pos$是一個已經記錄的值,$R$爲以$pos$爲中心的迴文串的最長右邊界,而後如今要求$p[i]$

$j$是$i$關於$pos$的對稱點,也就是說$j=2*pos-i$

那麼這個時候分爲兩種狀況

1.$j$的最長迴文沒有跳出$L$

由於$[L...R]$是一個迴文串,因此$i$的迴文和$j$的迴文相同,即$p[i]=p[j]$

2.$j$的最長迴文越過了$L$

若是在這種狀況下,$j$的最長迴文就不必定是$i$的最長迴文了。而後黃色那塊確定仍是知足條件的

因此綜上,$p[i]=min(p[2*pos-1],R-i)$

而後剩下的那部分怎麼辦?暴力直接算

我沒口胡,真的

時間複雜度

時間複雜度是$O(n)$的

爲啥嘞?

若是$p[i]$能直接求確定是$O(1)$的

而後若是須要上暴力那麼每一次都能讓$R$嚴格右移

由於串長只有$O(n)$,因此暴力次數最多$O(n)$

上板子吧,板子抄的zzk大爺的

洛谷P3805 【模板】manacher算法

 1 // luogu-judger-enable-o2
 2 //minamoto
 3 #include<iostream>
 4 #include<cstdio>
 5 #include<cstring>
 6 using namespace std;
 7 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
 8 char buf[1<<21],*p1=buf,*p2=buf;
 9 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
10 const int N=1.1e7+5;
11 int p[2*N];char s[N],now[N*2];
12 inline bool is(char c){return c!='\n'&&c!=EOF;}
13 inline void read(char *s){
14     int len=0;char ch;while(is(ch=getc())) s[++len]=ch;s[++len]=0;
15 }
16 int manacher(char *s){
17     int len=strlen(s+1);
18     for(int i=1;i<=len;++i) now[2*i-1]='%',now[2*i]=s[i];
19     now[len=len*2+1]='%';
20     int pos=0,R=0;
21     for(int i=1;i<=len;++i){
22         if(i<R) p[i]=min(p[2*pos-i],R-i);else p[i]=1;
23         while(i-p[i]>=1&&i+p[i]<=len&&now[i-p[i]]==now[i+p[i]]) ++p[i];
24         if(i+p[i]>R) pos=i,R=i+p[i];
25     }
26     int mx=0;
27     for(int i=1;i<=len;++i) cmax(mx,p[i]-1);
28     return mx;
29 }
30 int main(){
31 //    freopen("testdata.in","r",stdin);
32     read(s);
33     printf("%d\n",manacher(s));
34     return 0;
35 }

不過我有個地方還沒弄明白,就是上面求$p[i]$的時候,我寫成這樣也能A

1     for(int i=1;i<=len;++i){
2         if(i<R) p[i]=min(p[2*pos-i],R-i);else p[i]=1;
3         while(i-p[i]>=1&&i+p[i]<=len&&now[i-p[i]]==now[i+p[i]]) ++p[i];
4         if(i+p[i]-1>R) pos=i,R=i+p[i]-1;
5     }

話說若是我寫成 p[i]=min(p[2*pos-i],R-i+1) 我還能理解爲是開區間和閉區間的不一樣……話說一個閉區間一個開區間爲啥沒問題啊……

先坑着,等會了再來填坑

洛谷P4555 [國家集訓隊]最長雙迴文串

首先雙迴文串確定是拼接而成的

那麼咱們就記錄一下每個字符分別做爲迴文串的最左端(最右端)時,對應的中心最左(最右)在哪裏

而後就能夠經過拼接獲得雙迴文串了

 1 //minamoto
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 using namespace std;
 6 template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
 7 const int N=1e5+5;
 8 int p[2*N];char s[N],now[N*2];int L[N<<1],R[N<<1],len,ans;
 9 void manacher(char *s){
10     len=strlen(s+1);
11     for(int i=1;i<=len;++i) now[2*i-1]='%',now[2*i]=s[i];
12     now[len=len*2+1]='%';
13     int pos=0,R=0;
14     for(int i=1;i<=len;++i){
15         if(i<R) p[i]=min(p[2*pos-i],R-i);else p[i]=1;
16         while(i-p[i]>=1&&i+p[i]<=len&&now[i-p[i]]==now[i+p[i]]) ++p[i];
17         if(i+p[i]>R) pos=i,R=i+p[i];
18     }
19 }
20 int main(){
21 //    freopen("testdata.in","r",stdin);
22     scanf("%s",s+1);
23     manacher(s);
24     for(int i=1,pos=1;i<=len;++i)
25     for(;pos<=i+p[i]-1;++pos) L[pos]=i;
26     for(int i=len,pos=len;i;--i)
27     for(;pos>=i-p[i]+1;--pos) R[pos]=i;
28     for(int i=1;i<=len;++i) cmax(ans,R[i]-L[i]);
29     printf("%d\n",ans);
30     return 0;
31 }

洛谷P1659 [國家集訓隊]拉拉隊排練

由於題目要求的是奇數迴文,因此連$\%$都不必加了,直接上manacher就行了

而後由於一個半徑爲$n$的迴文串存在,那麼$n-1$,$n-2$的迴文串也一定存在

因此要開一個桶存一下前綴和

而後還要用一下快速冪

而後就差很少了

 1 //minamoto
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 #define ll long long
 6 using namespace std;
 7 const int N=1e6+5,mod=19930726;
 8 char s[N];int p[N],c[N],n;ll ans=1,sum=0,k;
 9 inline ll qpow(ll a,ll b){
10     ll res=1;
11     while(b){
12         if(b&1) (res*=a)%=mod;
13         (a*=a)%=mod,b>>=1;
14     }
15     return res;
16 }
17 int main(){
18 //    freopen("testdata.in","r",stdin);
19     scanf("%d%lld",&n,&k);
20     scanf("%s",s+1);
21     for(int i=1,pos=0,R=0;i<=n;++i){
22         p[i]=i<R?min(p[2*pos-i],R-i):1;
23         while(i-p[i]>=1&&i+p[i]<=n&&s[i-p[i]]==s[i+p[i]]) ++p[i];
24         if(i+p[i]-1>R) pos=i,R=i+p[i]-1;
25         ++c[2*p[i]-1];
26     }
27     (n&1)?0:(--n);
28     for(int i=n;i;i-=2){
29         sum+=c[i];
30         if(sum>k){(ans*=qpow(i,k))%=mod;break;}
31         (ans*=qpow(i,sum))%=mod,k-=sum;
32     }
33     printf("%lld\n",sum<k?-1:ans);
34     return 0;
35 }
相關文章
相關標籤/搜索