1. 程式人生 > >3068 最長迴文(馬拉車演算法模板)

3068 最長迴文(馬拉車演算法模板)

Problem Description
給出一個只由小寫英文字元a,b,c...y,z組成的字串S,求S中最長迴文串的長度.
迴文就是正反讀都是一樣的字串,如aba, abba等
Input
輸入有多組case,不超過120組,每組輸入為一行小寫英文字元a,b,c...y,z組成的字串S
兩組case之間由空行隔開(該空行不用處理)
字串長度len <= 110000
Output
每一行一個整數x,對應一組case,表示該組case的字串中所包含的最長迴文長度.
Sample Input
aaaa
abab
Sample Output
4
3
#include <iostream>
#include <cstdio>
#include <cstring>
#define Max 110005
using namespace std;
char Ma[Max*2];
int Mp[Max*2];
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;
		}
	}
}
char s[Max];
int main ()
{
	while (scanf ("%s",s) != EOF){
		int len = strlen(s),ans = 0;
		Manacher(s,len);
		for (int i = 0;i < 2*len+2; i++){
			ans = max(ans,Mp[i] - 1);
		}
		cout << ans <<endl;
	}
	return 0;
}

馬拉車演算法網上解析也很多,但主要一句話

Mp[i] = mx>i?min(Mp[2*id-i],mx-i):1;
大多說的含糊不清。

MP[i]代表當前下標i為中心的字串的迴文串半徑,馬拉車主要就是優化了每次試探MP[i]的時候不一定需要從1開始慢慢向兩邊移動來試探。


id是已知的最長的迴文串的中心,我們可以發現i關於id對稱是j。由於i從2開始列舉過來,早就經過了j的位置,所以j位置的最長迴文串已經確定如圖所示,假設j的迴文串完全被id的迴文串所包圍,那麼,由迴文串關於中心點對稱的特性可以保證,i點的迴文串的長度最少就是j點回文串的長度。即如果迴文串的子串也是迴文串,那麼這個子串關於主串中心對稱而得的子串也是一個迴文串。

接下來要確定的就是通過j點所能確定的i點回文串的長度最多是多少。首先應該明確,如果i點跑到mx(id點回文串所確定的範圍邊界)外面去了,那麼j點無論如何縮減範圍都不可能是id迴文串的子串,就不滿足上面加粗的結論了。就一定只能從1開始慢慢試探。這就是當mx  < i的時候,MP[i] = 1的原因了。

接下來還有兩種情況

一種就是上圖中,j所確定的迴文串完全被包含,即整個串都是其子串。那麼i的可確定迴文串範圍就是j的迴文串範圍,MP[i]就變成了MP[j]。

還有一種情況就是j的迴文串已經超出了mx的範圍


對於紅線以外的區域完全未知,所以必須將MP[j]減去紅線外的範圍才是i的可確定範圍。或者理解為只有兩端都去掉外面的部分之後,剩下的才是id迴文串的子串,才可以對稱過去成為i的迴文串。然後再在已確定的範圍基礎上向兩邊擴充套件。