算法笔记 Manacher

简单介绍

Manacher ,中文马拉车,是一种能在 \(O(n)\) 的时间内找到最长回文子串的优秀算法

问题引入

问题:给定字符串 \(s\) 求出 \(s\)最长回文子串并输出长度
\(|S|\le 10^7\)

思考

算法 \(1\)

我会暴力!
\(O(n^2)\) 枚举所有子串 ,\(O(n)\) 判断
时间复杂度 \(O(n^3)\)

算法 \(2\)

暴力太浪费时间
分析回文串的特质
容易发现 对于长度为奇数每个回文串 一定有一个对称中心
对于偶数 同理
因此我们 \(O(n)\) 枚举每个对称中心 暴力往两端拓展即可
鉴于分类讨论麻烦 我们在每个字符间插入特殊符号即可避免分类讨论


为了不越界判断 通常我们会在 \(s_0\) 这里插入一个与所有字符不一样的字符
这样做是为了判断超界 思考一下 不这样做 \(s_0\)\(s_{n+1}\) 两个字符是相同的 这样会一直超界匹配下去

建串 code

void build()
{
	n=strlen(t+1);
	s[0]='>',s[1]='#';
	for(int i=1;i<=n;i++)
		s[i<<1]=t[i],s[(i<<1)|1]='#';
	n=(n<<1)|1;
}

其它算法

哈希 + 二分 :判断用哈希和二分 时间复杂度 \(O(n\log n)\)
哈希 + 答案单调:不断扩展 时间复杂度为大常数 \(O(n)\)

马拉车

马拉车是一个神奇的算法 它能在看似 \(O(n^2)\) 的复杂度做到 \(O(n)\) 处理
时间复杂度浪费在多次判断字符串相等
因此 引入一个变量 \(mr\)\(mid\) 表示过程到的最右边于中点

如图 对称区间为 \(x\to mr\)
假如 \(i\)\(mid\to mr\) 之间
那么 我们可以借助 \(i\) 的对称点 \(j\) 快速更新答案
但是又不能超过 \(x\) 左边 因为左边并不能保证对称的性质
所以可以预处理为 \(p_i=\min(p_j,mr-i+1)\)
预处理完后进行暴力扩展
对于 \(j\) 的计算 使用中点公式即可
\(\because (i+j)/2=mid\therefore j=mid\times 2-i\)
对于不在区间内的 直接暴力扩展即可

code

#include<bits/stdc++.h>
#define N 22000005
using namespace std;
int n,cnt;
char t[N],s[N];
void build()
{
	n=strlen(t+1);
	s[0]='>',s[1]='#';
	for(int i=1;i<=n;i++)
		s[i<<1]=t[i],s[(i<<1)|1]='#';
	n=(n<<1)|1;
}
int ans;
int mr,mid;
int p[N];
void solve()
{
	for(int i=1;i<=n;i++)
	{
		if(i<=mr) p[i]=min(p[mid*2-i],mr-i+1);
		while(s[i-p[i]]==s[i+p[i]]) p[i]++;
		if(i+p[i]>mr) mr=i+p[i]-1,mid=i;
		ans=max(ans,p[i]-1);
	}
}
int main()
{
	scanf("%s",t+1);
	build();
	solve();
	printf("%d",ans);
	return 0;
}

关于时间复杂度

不会证明
感性理解 \(mr\) 是递增的

posted @ 2024-02-20 20:33  g1ove  阅读(14)  评论(1编辑  收藏  举报