1. 程式人生 > >hihocoder 1032 最長迴文子串 (Manacher演算法 詳解+模板)

hihocoder 1032 最長迴文子串 (Manacher演算法 詳解+模板)

時間限制:1000ms 單點時限:1000ms 記憶體限制:64MB

描述

   小Hi和小Ho是一對好朋友,出生在資訊化社會的他們對程式設計產生了莫大的興趣,他們約定好互相幫助,在程式設計的學習道路上一同前進。

   這一天,他們遇到了一連串的字串,於是小Hi就向小Ho提出了那個經典的問題:“小Ho,你能不能分別在這些字串中找到它們每一個的最長迴文子串呢?”

   小Ho奇怪的問道:“什麼叫做最長迴文子串呢?”

   小Hi回答道:“一個字串中連續的一段就是這個字串的子串,而回文串指的是12421這種從前往後讀和從後往前讀一模一樣的字串,所以最長迴文子串的意思就是這個字串中最長的身為迴文串的子串啦~”

   小Ho道:“原來如此!那麼我該怎麼得到這些字串呢?我又應該怎麼告訴你我所計算出的最長迴文子串呢?

   小Hi笑著說道:“這個很容易啦,你只需要寫一個程式,先從標準輸入讀取一個整數N(N<=30),代表我給你的字串的個數,然後接下來的就是我要給你的那N個字串(字串長度<=10^6)啦。而你要告訴我你的答案的話,只要將你計算出的最長迴文子串的長度按照我給你的順序依次輸出到標準輸出就可以了!你看這就是一個例子。”

提示一提示二提示三提示四 樣例輸入
3
abababa
aaaabaa
acacdas
樣例輸出
7
5
3 


題目分析:Manacher演算法可以在O(n)的時間複雜度內解決最長迴文子串問題,下面介紹一下這個演算法

首先對於一個任意長度的字串,通過插入無關字元法均可以將其變成奇數長度,如aba => #a#b#a#,abba => #a#b#b#a#,為了解決邊界問題可以直接在最前面再加上一個無關字元,令cur為當前能延伸到最右端的迴文子串的中心位置,p[cur]表示當前能延伸到最右端的迴文子串的迴文半徑,而p[cur] + cur就是當前能延伸到的最右端,當前位置i如果在其範圍之外,即p[cur] + cur < i則p[i] = 1(自己另起一段迴文子串),如果p[cur] + cur >= i,也就是當前位置在其範圍內,則此時p[i] = min(p[cur * 2 - i],p[cur] + cur - i),這裡分兩種情況,1) p[cur * 2 - i] > p[cur] + cur - i,也就是說以i當前的對稱點為中心的迴文子串範圍在當前cur為中心的迴文子串的最左端的左邊,則這時p[i] = p[cur] + cur - i;p[cur] + cur - i指的是當前cur為中心的迴文串的最右端到當前點i的距離,2) p[cur * 2 - i] <= p[cur] + cur - i,情況類似上面,畫圖很容易看出來,算出p[i],則以當前的i為中心向兩端擴充套件,若擴展出來的最右端超過原來的最右端則更新cur

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int const MAX = 1e6 + 5;
char s[MAX << 1];
int p[MAX << 1];

int Manacher()
{
	int len = strlen(s);
	for(int i = len; i >= 0; i--)
	{
		s[(i << 1) + 2] = s[i];
		s[(i << 1) + 1] = '#';
	}
	s[0] = '*';
	int cur = 0, ans = 0;
	for(int i = 2; i < 2 * len + 1; i++)
	{
		if(p[cur] + cur >= i)
			p[i] = min(p[(cur << 1) - i], p[cur] + cur - i);
		else
			p[i] = 1;
		while(s[i - p[i]] == s[i + p[i]])
			p[i] ++;
		if(p[cur] + cur < i + p[i])
			cur = i;
		ans = max(ans, p[i]);
	}
	return ans - 1;
}

int main()
{
	int n;
	scanf("%d", &n);
	while(n --)
	{
		scanf("%s", s);
		printf("%d\n", Manacher());
	}
}