manacher算法求最長回文子序列
阿新 • • 發佈:2018-06-05
manacher || pac def 比較 防止 http header left
,因此我們可以利用
一:背景
給定一個字符串,求出其最長回文子串。例如:
- s="abcd",最長回文長度為 1;
- s="ababa",最長回文長度為 5;
- s="abccb",最長回文長度為 4,即bccb。
以上問題的傳統思路大概是,遍歷每一個字符,以該字符為中心向兩邊查找。其時間復雜度為O(n^2),效率很差。
1975年,一個叫Manacher的人發明了一個算法,Manacher算法(中文名:馬拉車算法),該算法可以把時間復雜度提升到O(n)。下面來看看馬拉車算法是如何工作的。
二:算法過程分析
由於回文分為偶回文(比如 bccb)和奇回文(比如 bcacb),而在處理奇偶問題上會比較繁瑣,所以這裏我們使用一個技巧,具體做法是:在字符串首尾,及各字符間各插入一個字符(前提這個字符未出現在串裏)。
舉個例子:s="abbahopxpo"
,轉換為s_new="$#a#b#b#a#h#o#p#x#p#o#"
(這裏的字符 $ 只是為了防止越界,下面代碼會有說明),如此,s 裏起初有一個偶回文abba
和一個奇回文opxpo
,被轉換為#a#b#b#a#
和#o#p#x#p#o#
,長度都轉換成了奇數。
定義一個輔助數組int p[]
,其中p[i]
表示以 i 為中心的最長回文的半徑,例如:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
s_new[i] | $ | # | a | # | b | # | b | # | a | # | h | # | o | # | p | # | x | # | p | # |
p[i] | 1 | 2 | 1 | 2 | 5 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 4 | 1 | 2 | 1 |
可以看出,p[i] - 1
正好是原字符串中最長回文串的長度。
接下來的重點就是求解 p 數組,如下圖:
設置兩個變量,mx 和 id 。mx 代表以 id 為中心的最長回文的右邊界,也就是mx = id + p[id]
。
假設我們現在求p[i]
,也就是以 i 為中心的最長回文半徑,如果i < mx
,如上圖,那麽:
if (i < mx) p[i] = min(p[2 * id - i], mx - i);
2 * id - i
為 i 關於 id 的對稱點,即上圖的 j 點,而p[j]
表示以 j 為中心的最長回文半徑
p[j]
來加快查找。
三:代碼
//指定位置判斷回文,此題為指定包含最後一個的最長回文序列。 char ma[maxn*2], s[maxn]; int mp[maxn*2]; int ans,Mlen; void Manacher(char s[],int len) { int l=0; ma[l++]=‘$‘; ma[l++]=‘#‘; for(int i=0; i<len; i++) { ma[l++]=s[i]; ma[l++]=‘#‘; } ma[l]=0; int mx=0,id=0; for(int i=0; i<l; i++) { mp[i]=mx>i?min(mp[2*id-i],mx-i):1; while(ma[i+mp[i]]==ma[i-mp[i]]) mp[i]++; if(i+mp[i]>mx) { mx=i+mp[i]; id=i; } // 這裏可以check(ma[i]) ans=max(ans,mp[i]-1); if(mp[i]-1+i==l-1) Mlen=max(Mlen,mp[i]-1); } } int main() { int T; cin>>T; int kcase = 1; while(T--) { memset(ma,0,sizeof(ma)); memset(mp,0,sizeof(mp)); cin>>s; int len=strlen(s); ans=0; Mlen=0; Manacher(s,len); if(ans == len) printf("Case %d: %d\n", kcase++, ans); else printf("Case %d: %d\n", kcase++, len - Mlen + len); } }
四:題目
這個題目就是在原來的基礎上添加了一個判斷條件,看清楚在哪裏添加。
//101350I - 2017 ACM Arabella Collegiate Programming Contest - Mirrored String II #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1010; char ma[maxn*2]; int mp[maxn*2]; char s[maxn]; int check(char zzz) { if(zzz==‘A‘||zzz==‘H‘||zzz==‘I‘||zzz==‘M‘||zzz==‘O‘||zzz==‘#‘|| zzz==‘T‘||zzz==‘U‘||zzz==‘V‘||zzz==‘W‘||zzz==‘X‘||zzz==‘Y‘) return 1; return 0; } void Manacher(char s[],int len) { int l=0; ma[l++]=‘$‘; ma[l++]=‘#‘; for(int i=0; i<len; i++) { ma[l++]=s[i]; ma[l++]=‘#‘; } ma[l]=0; int mx=0,id=0; for(int i=0; i<l; i++) { mp[i]=mx>i?min(mp[2*id-i],mx-i):1; while(check(ma[i+mp[i]])&&ma[i+mp[i]]==ma[i-mp[i]])//在這裏添加check { mp[i]++; } if(i+mp[i]>mx) { mx=i+mp[i]; id=i; } } } int main() { int T; cin>>T; while(T--) { cin>>s; int len=strlen(s); Manacher(s,len); int ans=0; for(int i=0; i<len*2+2; i++) if(check(ma[i]))//這裏添加check ans=max(ans,mp[i]-1); cout<<ans<<endl; } }
manacher算法求最長回文子序列