轉自:http://bbs.dlut.edu.cn/bbstcon.php?board=Competition&gid=23474
其實原文說得是比較清楚的,只是英文的,我這裏寫一份中文的吧。
首先:大家都知道什麼叫回文串吧,這個算法要解決的就是一個字符串中最長的迴文子串有多長。這個算法可以在O(n)的時間複雜度內既線性時間複雜度的情況下,求出以每個字符爲中心的最長迴文有多長,
這個算法有一個很巧妙的地方,它把奇數的迴文串和偶數的迴文串統一起來考慮了。這一點一直是在做迴文串問題中時比較煩的地方。這個算法還有一個很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重複匹配。
算法大致過程是這樣。先在每兩個相鄰字符中間插入一個分隔符,當然這個分隔符要在原串中沒有出現過。一般可以用‘#’分隔。這樣就非常巧妙的將奇數長度迴文串與偶數長度迴文串統一起來考慮了(見下面的一個例子,迴文串長度全爲奇數了),然後用一個輔助數組P記錄以每個字符爲中心的最長迴文串的信息。P[id]記錄的是以字符str[id]爲中心的最長迴文串,當以str[id]爲第一個字符,這個最長迴文串向右延伸了P[id]個字符。
原串: w aa bwsw f d
新串: # w # a # a # b # w # s # w # f # d #
輔助數組P: 1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
這裏有一個很好的性質,P[id]-1就是該回文子串在原串中的長度(包括‘#’)。如果這裏不是特別清楚,可以自己拿出紙來畫一畫,自己體會體會。當然這裏可能每個人寫法不盡相同,不過我想大致思路應該是一樣的吧。
好,我們繼續。現在的關鍵問題就在於怎麼在O(n)時間複雜度內求出P數組了。只要把這個P數組求出來,最長迴文子串就可以直接掃一遍得出來了。
由於這個算法是線性從前往後掃的。那麼當我們準備求P[i]的時候,i以前的P[j]我們是已經得到了的。我們用mx記在i之前的迴文串中,延伸至最右端的位置。同時用id這個變量記下取得這個最優mx時的id值。(注:爲了防止字符比較的時候越界,我在這個加了‘#’的字符串之前還加了另一個特殊字符‘$’,故我的新串下標是從1開始的)
好,到這裏,我們可以先貼一份代碼了。
複製代碼
void pk()
{
int i;
int mx = 0;
int id;
for(i=1; i<n; i++)
{
if( mx > i )
p[i] = MIN( p[2*id-i], mx-i );
else
p[i] = 1;
for(; str[i+p[i]] == str[i-p[i]]; p[i]++)
;
if( p[i] + i > mx )
{
mx = p[i] + i;
id = i;
}
}
}
代碼是不是很短啊,而且相當好寫。很方便吧,還記得我上面說的這個算法避免了很多不必要的重複匹配吧。這是什麼意思呢,其實這就是一句代碼。
if( mx > i )
p[i] = MIN( p[2*id-i], mx-i );
//這裏p[i]不從1開始會縮短好多運算,
//p[i]的縮短是根據當前一次比較最遠的長度mx>i的時候,即這個時候才存在優化,優化的方法是把i~mx這段元素用j來替換因爲迴文數兩邊是相等的
//所以j=2*id-i;但是這裏一個新的問題是i~mx之間其實向後(只是向前計算過)是沒有計算過迴文的,所以如果他是向後迴文的話即以mx爲中心
//的迴文應該直接跳躍mx-i,因此在這裏選擇的是MIN(p[2*id-i],mx-i);這些事自己的理解記錄下來免得忘了
就是當前面比較的最遠長度mx>i的時候,P[i]有一個最小值。這個算法的核心思想就在這裏,爲什麼P數組滿足這樣一個性質呢?
(下面的部分爲圖片形式)
此主題相關圖片如下:8_56_4f13d6e009ae79e.png (88KB)
兩個基本題:hdu 3068 poj 3974
#include<cstdio>
#include<cstring>
const int M = 110010*2;
char str[M];//start from index 1
int p[M];
char s[M];
int n;
void checkmax(int &ans,int b){
if(b>ans) ans=b;
}
inline int min(int a,int b){
return a<b?a:b;
}
void kp(){
int i;
int mx = 0;
int id;
for(i=1; i<n; i++){
if( mx > i )
p[i] = min( p[2*id-i], p[id]+id-i );
else
p[i] = 1;
for(; str[i+p[i]] == str[i-p[i]]; p[i]++) ;
if( p[i] + i > mx ) {
mx = p[i] + i;
id = i;
}
}
}
void pre()
{
int i,j,k;
n = strlen(s);
str[0] = '$';
str[1] = '#';
for(i=0;i<n;i++)
{
str[i*2 + 2] = s[i];
str[i*2 + 3] = '#';
}
n = n*2 + 2;
str[n] = 0;
}
void pt()
{
int i;
int ans = 0;
for(i=0;i<n;i++)
checkmax(ans, p[i]);
printf("%d\n", ans-1);
}
int main()
{
int T,_=0;
while( scanf("%s", s) !=EOF )
{
pre();
kp();
pt();
}
return 0;
}
還有 codeforces 7D : k迴文