演算法隨筆——manacher

codwarm發表於2024-05-04

非常好學習資料

manacher

求最長迴文子串

暴力

列舉迴文中心 \([1,n]\),暴力向兩邊擴充,然後 \(checkmax\)。時間複雜度 \(O(n^2)\)
可以用二分雜湊最佳化至 \(O(n \log n)\)

演算法思路

當求解第 \(i\) 個字元為迴文中心的時候,已經知道了 \([1,i-1]\) 之間的資訊。
於是引入
\(p[i]\) :以 i 為中心的最長迴文半徑(即迴文串的一半)
\(r\) :迴文串右端點最遠可以到達的位置,則該設迴文串為 \([l,r]\)
\(m\)\([l,r]\) 迴文串的中心,\(m = (l + r)/2\),始終滿足 \(m < i\)
\(k\) :i 關於 m 的對稱點,$k = 2 * m -i $
image
image

因此 \(p[i] = min(r-i+1,p[2*m-i])\)

小tips

因為迴文串有奇有偶,因此為了避免討論,在每個字元之前和首尾插入一個 '#',可以保證每個迴文串長度均是奇數,答案即為 \(max{p[i]} - 1\)

char old[N],str[N];

int p[N],r,m;

int main()
{
	scanf("%s",old+1);
	int len = 0;
	//插入字元 初始化
	str[++len] = '#';
	for (int i = 1,l = strlen(old+1);i <= l;i++)
		str[++len] = old[i],str[++len] = '#';
	for (int i = 1;i <= len;i++)
	{
		if (i > r) p[i] = 1;
		else p[i] = min(p[2*m-i],r-i+1);
		while (i+p[i] <= len && i - p[i] >= 1 && 
		str[i+p[i]] == str[i-p[i]]) p[i]++; //暴力擴充
		//更新中心和最遠右端點
		if (i + p[i] - 1 > r) r = i + p[i] - 1,m = i;
	}
	int ans = 0;
	for (int i = 1;i <= len;i++ ) ans = max(p[i],ans);
	cout << ans - 1 << endl;
	//答案等於(整條迴文串-1)/2
	//即(p[i]*2-1 -1)/2 = p[i]-1
	return 0;
}