Manacher算法-马拉车(麻雀)-字符串回文

Manacher算法-马拉车(麻雀)

字符串回文

这个算法。和原先学习的KMP都是处理字符串的。只不过麻雀是专门针对回文串的。我是最近CF补题的时候碰到这个算法的。特此记录一下。说实话。原先学数据结构的时候学的KMP。现在应该完全忘光了把。。啊哈哈。明天再补补。

举个例子:
回文串分为奇回文和偶回文。奇回文的中心是中间的字符。偶回文的中心是两个字符中间的位置。不好同一处理。
所以我们加如下操作。比如aabbaa
增加为#a#a#b#b#a#a#
这样不管是奇回文还是偶回文都是字符啦。偶回文是#。奇回文还是原来的。

我们用数组p[]保存当前下标index作为中心时的回文序列长度半径
所以我们以当前下标index为中心的回文串的最长长度就是p[i] - 1(这个地方不证明了。自己随便举几个例子就明白了)

mx记录当前已操作序列的最右的回文边界。就是最长的回文序列的最右边的地方。
id记录最长回文序列的中心位置。

1
当我们遍历到i时。求p[i]
我们找到i的对称点j。这个地方的下标很简单求出来。(id - (i - id))即可
如图所示mx的对称点也标出来了。上面解释了。mx是最优边界。id是最长回文串的中心。
p[j]我们已经求出来了。
本算法的核心代码:

2
上面的p[2*id - i]就是p[j];
我们讨论一下p[j]的大小。
3
如果p[j]的回文串只有这么长。
也就是没有大于mx - i的长度
4
图中标记的橙色部分相等。。这个简单。
也就是以j为中心得回文串没有超过这个长度。
因为对称。所以此时我们赋值p[i] = p[j];这个是此时p[i]的最小长度。就在这个地方节约时间的。
至于p[i]的值有没有可能更长。我们后面暴力跑一遍就行。

如果p[j]的回文串大于了这个长度。
也就是这样:
5
已经超过了mx的对称点的长度。
那我们只能将p[i]赋值为mx - i
因为这个mx - i是我们现在已知的最小长度。
因为mx对称点左边的字符和mx右边的字符不等呀。不然mx肯定不在当前位置了。。
之后我们暴力跑一遍判断即可。
我当时在想为啥这两部分的p[]是一一对称的。
6
傻坐在那想了半天。。
后来举了个例子画了个图就知道了。
因为mx和mx对称点范围内就是我们求得最长回文呀。id左边和id右边肯定是一一对称的呀。。。害~

上面解释完了就应该很好理解了。
然后我们在制作新的字符串的时候注意边界位置。
第一个字符初始化为$
最后一个字符就是结束标志’\0’就行。避免溢出。

时间复杂度控制在O(N)范围
棒~

模板代码:
我最后输出的是最长的回文串的长度。

#include <bits/stdc++.h>
using namespace std;
const int N = 2e7 + 10;

string s;
char ss[N];
int p[N];

int Init()//形成新的字符串 
{
	int n = s.size();
	ss[0] = '$';//边界处理
	ss[1] = '#';
	int i = 2;
	for (int j = 0; j < n; j++)
	{
		ss[i++] = s[j];
		ss[i++] = '#';
	} 
	ss[i] = '\0';
	return i;
}

int Manacher()
{
	int len = Init();
	int maxx = -1;//最长回文串长度
	int id = 1;
	int mx = 0;
	
	for (int i = 1; i <= len; i++)
	{
		if (i < mx)
		{
			p[i] = min(p[2 * id - i], mx - i);
		}
		else
		{
			p[i] = 1;
		}
		while (ss[i - p[i]] == ss[i + p[i]])
		{
			p[i]++;
		}
		if (mx < i + p[i])
		{
			id = i;
			mx = i + p[i];
		}
		maxx = max(maxx, p[i] - 1);
	} 
	return maxx;
}

int main()
{
	cin >> s;
	cout << Manacher() << endl;
	return 0;
} 
发布了112 篇原创文章 · 获赞 3 · 访问量 2625

猜你喜欢

转载自blog.csdn.net/qq_44624316/article/details/105018474