【例題】
【思路】
最長迴文子串是個非常經典的問題,Manacher算法是解決它的O(n)優秀算法。
該算法提出在字符串相鄰字符間插入字符,從而在中心拓展時無需考慮串長度的奇偶性(顯然,對於任意長度爲n的串,有n-1個間隔,故而補全串長度爲2n-1,總爲奇數)。
舉個例子:原串str爲abababa,長度爲7。則補全串s爲$#a#b#a#b#a#b#a#*,長度爲17。其中#、$和*不屬於原串字符集合,#爲插入字符,$和*用來標記補全串邊界,這方便了中心拓展比較,同時避免索引溢出。
設數組r,r[i]表示補全串s中,以字符s[i]爲中心的迴文串半徑長度(半徑含中心字符s[i])。
一旦得出數組r,則只需遍歷r並將ans置爲最大的r[i]值,輸出ans-1即爲原串中迴文串的最大長度。
可以證明,經過上面的補全後,對於任意i,補全串中以s[i]爲中心的迴文串去掉所有#後,長度爲r[i]-1,即對應的原串中迴文子串的長度。
如何求出數組r呢?顯然在求r[i]時,要充分利用r[1]、r[2]……r[i-1]這些已經求出的信息。先在這裏寫出數學表達。
由集合Q確定r[i]的最小值後,還需嘗試性地增大r[i]向外圍擴展比較,直到遇見s[i-r[i]]≠s[i+r[i]],則已找到以s[i]爲中心的最長迴文串。
上面的數學表述雖然嚴謹,但不夠具象,下面對該數學形式作通俗闡述。
事實上,j+r[j]反映了1,2……i-1中已經找到的所有迴文串的最大覆蓋範圍。如果j+r[j]<i,則前面的結果對求r[i]並沒有作用,我們只能暫時置r[i]=1(一個字符本身,也是一個迴文串),然後再向外拓展比較。否則,i則在前面的覆蓋範圍內,那麼首先找到s[i]關於s[j]的對稱字符s[j*2-i],則s[i]、s[j*2-i]距以s[j]爲中心的迴文串邊緣的長度均爲j+r[j]-i,根據迴文串的對稱性可得,若r[j*2-i]< j+r[j]-i(以s[j*2-i]爲中心的迴文串是以s[j]爲中心的迴文串的子串),則在覆蓋範圍內可以確定r[i]=r[j*2-i] ; 若r[j*2-i]> j+r[j]-i,則以s[i]爲中心的迴文串可一直拓展至覆蓋邊緣。覆蓋範圍外的部分,則需要我們嘗試增大r[i],繼續老老實實地比較。
更形象的表述,則可見下圖(綠色爲重疊部分,藍色爲s[j]中心串,黃色爲s[j*2-i]中心串突出部分):
【代碼】
#include <stdio.h>
#include <string.h>
#define maxSize 1000010
#define min(x,y) (x<y)? x:y
long int manacher(const char *str)
{
long int i,len=strlen(str);
char s[maxSize*2+3];
s[0]='$'; s[len*2+1]='#'; s[len*2+2]='*';
for (i=0;i<len;i++) {s[2*i+1]='#'; s[2*i+2]=str[i];}
long int r[maxSize*2+3], j=1; r[1]=1;
for (i=2;i<=len*2;i++)
{
if (r[j]+j>i) r[i]=min(r[j]+j-i, r[2*j-i]); else r[i]=1;
while (s[i-r[i]]==s[i+r[i]]) r[i]++;
if (i+r[i]>j+r[j]) j=i;
}
long int ans=0;
for (i=1;i<=len*2;i++) if (r[i]>ans) ans=r[i];
return ans-1;
}
int main()
{
char str[maxSize];
int n,i;
scanf("%d",&n);
for (i=0;i<n;i++){scanf("%s",str); printf("%d\n",manacher(str));}
return 0;
}