HihoCode1032 最长回文子串 manacher算法
求最長回文子串的算法比較經典的是manacher算法
轉載自這里
首先,說明一下用到的數組和其他參數的含義:
(1)p[i] : 以字符串中下標為的字符為中心的回文子串半徑長度;
例如:abaa字符串,那么p[1]=2,(以b為中心的回文子串是aba,半徑長度為2。計算半徑時包括b本身)
所以,數組的最大值就是最長回文串的半徑。
(2)id
id 為當前已確定的邊界能伸展到最靠右的回文串的中心。
例如:
- 設黑色的線段表示字符串范圍
- 紅色的線段表示當前已經確定的回文子串在主串中的范圍
雖然左邊的紅色線段(左邊的回文子串)長度比右邊長,但是,記錄的中心是右邊紅色線段的中心,因為右邊的回文子串伸展更靠右。(下面將會解釋為什么要記錄這個id值)
manacher算法是從左到右掃描的,所以,在計算時,都是已知的。
manacher算法
假設現在掃描到主串下標為的字符,那么,以為中心的回文子串最可能和之前已經確定的哪個回文子串有交集? 沒錯,就是能伸展最靠右的回文子串,也就是以為中心的回文子串。至于這個交集有什么用,下面將解釋。
以i為中心的回文串和以id為中心的回文串如果有交集,會出現三種情況:
(1)
其中,2*id-i為i以id為中心的對稱位置。(2*id-i這個就不用多說了吧,計算一下就得到了)
第一種情況是(上圖),以2id-i為中心的回文子串左端超出了以id為中心的回文子串(綠色部分左端超出黑色部分左端)。那么,根據回文串的特點,知道以2id-i為中心的兩邊橙色部分是對稱的,同樣,若以id為中心,這兩段橙色部分又對稱id右邊兩段橙色部分,所以,以i為中心的兩段橙色部分也是回文串。
這種情況下, p[i]=p[id]+id-i (橙色部分的長度)
那么,以i為中心的橙色部分有沒有可能更長?這是不可能的,假設還可以更長,如下:
a和b對稱,b和c對稱,c和d對稱,最終得到a和d對稱,那么,以id為中心的回文串長度就不是下面黑色部分的長度了,而應左端加a右端加d,與已經求得的長度矛盾。
所以這種情況下: p[i]=p[id]+id-i
(2)
第二種情況是以2*id-i為中心的回文子串在以id為中心的回文子串內,如上圖。此時,p[i]=p[2*id-i] ,那么,以i為中心的綠色部分還可以伸展嗎?假設可以,如下:
同樣,c和d對稱,b和c對稱,a和d對稱,得到a和b對稱,那么以2*id-i為中心的回文子串長度就不是綠色部分的長度了,需要左端加a右端加b,與以求得的長度矛盾。
所以,這種情況下 p[i]=p[2*id-i]。
(3)
第三種情況是,以2*id-i為中心的回文子串左端與以id為中心的回文子串的左端恰好重合。則有。也就是說在的基礎上,還可能增加,即以i為中心的綠色部分還可能伸展。
所以,需要用一個while循環來確定它能增加多少。while循環為:
while (s[i - p[i]] == s[i + p[i]])++p[i];也就是判斷綠色兩端(下圖淺黑色線段)是否相同,如果相同,就可以不斷增加。(理解p[i]的意思,就理解這個循環了)
如果沒有出現交集(上面三種情況),那么就以i為中心點找最長回文子串(下面代碼中else的情況)。所以,算法主要是利用回文串的交集來減少計算。
如果我們將上面的情況總結起來,代碼將非常簡潔:
if (p[id] + id - 1 >= i) //沒有超出當前邊界最右的回文串,也就是上面出現交集三種情況中的一種p[i] = Min(p[2 * id - i], p[id] + id - i); else//如果沒有交集,就以它為中心求回文串長度p[i] = 1;while (s[i - p[i]] == s[i + p[i]])++p[i];最后,需要注意的是,上面的討論都是以某個字符為中心的回文串,比如像這樣的回文串:aabaa (長度為奇數)。但是,如果是這樣的回文串:aabbaa(長度為偶數),就沒法處理。
我們可以通過插入特殊符號(如‘#’)的辦法,將字符串統一為奇數長度,如aabaa 變為 #a#a#b#a#a# ;同理,aabbaa變為#a#a#b#b#a#a#
注意到,上面的代碼:
while (s[i - p[i]] == s[i + p[i]])++p[i];可能越界(超過頭或尾),我們可以通過頭尾也加入不相同的特殊符號處理,如aabaa 變為 $ #a#a#b#a#a# @。這種辦法為什么可行呢?我們舉個例子,還是以aabaa為例,它變成 $ #a#a#b#a#a# @。當i指向第一個a時(也就是i=2),這時,s[i-1]==s[i+1];繼續比較s[i-2]≠s[i+2](也就是比較$和a),就不會超過頭。所以,就避免了越界的現象。末尾加個@也是同樣的道理。
最長回文串的長度是maxlen-1
解釋
- p[i]為回文串的半徑,如果該半徑是以 # 開始,即 # s[i] #....# 則一定以 # 結束,所以 maxlen-1 ,恰好是 s[] 和 # 一樣多,也就是maxlen-1是原串以 i 為中心的回文串的長度
- 如果該半徑是以s[]開頭,即 ....# s[i-1] # s[i] # s[i+1] #.... ,顯然回文串的長度是p[i]-1
下面是hihoCoder的一道求最長回文子串的題:http://hihocoder.com/problemset/problem/1032
#include<iostream> #include<string> int Min(int a, int b) {if (a < b)return a;return b; }int LPS(std::string &s) {std::string new_s="";int s_len = s.length();new_s.resize(2 * s_len + 3);int new_s_len = new_s.length();new_s[0] = '$';new_s[1] = '#';new_s[new_s_len - 1] = '@';for (int i = 0,j=2; i < s_len; ++i){new_s[j++] = s[i];new_s[j++] = '#';}int *p = new int[new_s_len + 1];int id = 0;//記錄已經查找過的邊界最靠右回文串的中心int maxLPS = 1;p[0] = 1;for (int i = 1; i < new_s_len-1; ++i){if (p[id] + id - 1 >= i)//有交集的情況p[i] = Min(p[2 * id - i], p[id] + id - i);else//無交集的情況p[i] = 1;while (new_s[i - p[i]] == new_s[i + p[i]])//確定能伸展多少,上面if的情況是不會執行這個循環的++p[i];if (p[id] + id < p[i] + i)//重新確定伸展最右的回文子串中心id = i;if (p[i]>maxLPS)//保存當前最長回文子串的長度(還要-1)maxLPS = p[i];}delete[] p;return maxLPS - 1; }int main() {int N;std::string s;std::cin >> N;while (N--){std::cin >> s;std::cout<<LPS(s)<<std::endl;}return 0; }HDU3068 使用C語言字符數組來加快速度
#include<stdio.h> #include<algorithm> #include<string.h> #include<iostream> #include<math.h> #include<queue> #include<map> #include<vector> using namespace std; #define inf 0x3f3f3f3f #define ll long long const int maxn=2e5+10; char s[maxn],new_s[maxn*2];int LPS(){int s_len=strlen(s);int new_s_len=s_len*2+3;new_s[0]='$';new_s[1]='#';new_s[new_s_len-1]='@';int i,j;for(i=0,j=2;i<s_len;i++){new_s[j++]=s[i];new_s[j++]='#';}new_s[j]='\0';int *p=new int[new_s_len+1];int id=0;int maxLPS=1;p[0]=1;for(int i=1;i<new_s_len;i++){if(p[id]+id-1>=i)p[i]=min(p[2*id-i],p[id]+id-i);elsep[i]=1;while(new_s[i-p[i]]==new_s[i+p[i]])p[i]++;if(p[id]+id<p[i]+i)id=i;if(p[i]>maxLPS)maxLPS=p[i];}delete[] p;return maxLPS-1; } int main(){int ca=1;while(scanf("%s",s)!=EOF){memset(new_s,0,sizeof new_s);printf("%d\n",LPS());}return 0; }更加高效簡單的寫法
#include<stdio.h> #include<algorithm> #include<string.h> #include<iostream> #include<math.h> #include<queue> #include<map> #include<vector> using namespace std; #define inf 0x3f3f3f3f #define ll long long const int maxn=2e5+10; char s[maxn<<1]; int p[maxn<<1];//p[i]是回文串的半徑int LPS(){int len=strlen(s);int maxlen=1;//最長回文字符串的長度int id=0;for(int i=len;i>=0;i--){//插入len+1個”#“s[i+i+2]=s[i];s[i+i+1]='#';}//最終的S的長度是1~2*len+1s[0]='*';//防止while時p[i]越界for(int i=2;i<2*len+1;i++){if(p[id]+id>i)p[i]=min(p[2*id-i],p[id]+id-i);elsep[i]=1;while(s[i-p[i]]==s[i+p[i]])p[i]++;if(id+p[id]<i+p[i])id=i;if(maxlen<p[i])maxlen=p[i];}return maxlen-1; } int main(){while(scanf("%s",s)!=EOF){printf("%d\n",LPS());}return 0; }總結
以上是生活随笔為你收集整理的HihoCode1032 最长回文子串 manacher算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两种方法判断有向图是否有环【DFS】【拓
- 下一篇: HihoCode1721删除一个字符之后