zoukankan      html  css  js  c++  java
  • manacher算法笔记

    吐槽

    暑假上的字符串课没听懂几个字,所以现在像在学新算法一样

    好像就是在学新算法,不管了

    其实讲真的字符串的算法都比较巧妙啊!KMP,AC自动机,Manacher,SAM,这些算法都是思想非常巧妙的,而且代码比较短!所以 很难,学不会 要好好理解

    这是本蒟蒻字符串专题学习的第一个算法,为什么不是KMP?因为题单第一道题目就是用的Manacher(bushi .

    (图暂时没放,有时间画了就放上来)

    字符回文串算法

    好了扯谈结束.

    OI中一直有一个经典的问题,求回文子串。

    然后大概有三种算法来解决这个问题:

    • 中心扩展法
    • 动态规划法
    • Manacher算法

    这里讲的就是Manacher算法.

    为什么上面两个它们不行?

    第一个不能处理偶回文(找不到中心!)而且时间复杂度O((n^2)),

    第二个时间复杂度O((n^2)),显然也不大行的样子

    但是我们的Manacher算法,它的时间复杂度为O(n),并且可以适应奇回文和偶回文!这个算法大大滴好!

    所以我们用Manacher 来解决问题.

    Manacher 算法

    算法思想:

    将偶回文串转化为奇回文串,同时利用已经处理过的信息,借助对称(回文串具有对称的性质)来对当前处理的点进行扩展。

    规定算法概念:

    定义(R)当前扩展到的所有回文串的最右边界

    定义(pos) 是右边界为(R)且第一次扩展到(R)的时候对应的中心点

    定义(L)(R)关于(pos)的对称点(也就是以(pos)为对称中心的回文串的左端点),(L = 2* pos - R)

    定义(x)回文半径是我们求到的以(x)为中心的最长回文串的左边界到(x)的长度,规定为(p[x])

    定义(i)是当前处理到的,要求以(i)为中心的回文半径

    算法流程:

    第一步,先把偶回文转化为奇回文,这个的处理就是在原串的基础上,每两个字符中间加上一个奇奇怪怪的字符

    例如原串为: str = "a , b , b , a , b , a "
    转化后就为:str' = "^ , a , # , b , # , b , # , a , # , b , # , a , $ "

    放张图片:

    BnH0Ug.png

    你数一数是不是偶回文就变成了奇回文了呀?

    细心的小伙伴已经注意到了,第一个加入的字符和最后一个加入的字符不一样 !这个是为了处理边界,后面会用到

    第二步,对于新串中的每一个点求它们的回文半径.

    按照前面的规定,我们现在知道了(R)以及(pos),还有当前我们要求回文半径的点为(i),同时显然(i > pos),而且我们已经知道了扩展后的字符串的(i)前面字符的所有(p[j]),((0 <= j < i))

    我们规定(i)关于(pos)对称的点为(i') , (i' = 2 * pos - i)

    然后分情况讨论:

    以下内容为算法核心!!

    • (i) < (R):

    那么(i')就会大于(L)小于(pos)

    (1.)如果(p[i'] + i <= R),这就告诉我们,以(i')为中心的回文串被包含在回文串([L,R])(也就是当前右端点最右的回文串).

    那么因为([L,R])是一个回文串,这个串([L,R]) 就具有对称性,那么(p[i] = p[i'])

    (2.) 如果(p[i'] + i > R),这就告诉我们,以(i')为中心的回文串 一定不是 完全包含在回文串([L,R])中。

    但是至少我们可以知道,以(i')为中心的回文串包含在回文串([L,R])中间的部分,根据对称性,以(i)为中心的回文串的长度一定大于等于这部分,然后我们再进行暴力扩展.

    综合上面两种情况,我们可以得到一条核心代码:

    p[i] = min(p[i'],R - i + 1);//前面是代表的被完全包含的情况,后面则是没有被完全包含的情况,两种情况因为是求可行情况,所以是求min
    

    (i')实际代码是写成(2 * pos - i)

    • (i) >= (R)

    这没办法,前面的信息已经用不上了,暴力修改,同时更新(R)以及 (pos),此时初始化(p[i]等于1)

    核心代码:

          while(str[i + p[i]] == str[i - p[i]])p[i] ++;
           // 这里就是暴力扩展,然后前面的伏笔边界处理就在此用上了,因为边界字符不同,所以我们不会把边界算进去
    

    算法核心到此结束

    然后就算法至此完结撒花了,给出算法核心代码:

    int manacher()
    {
    	int R = 0 , pos = 0 ,maxlen = 0;
    	for(int i = 1 ; i <= lenstr ; i ++)
    	{
    		if(i <= R)
    			p[i] = min(p[2 * pos - i],R - i + 1);
    		else p[i] = 1;
    		while(str[i - p[i]] == str[i + p[i]])p[i] ++;//暴力枚举
    		if(i + p[i] - 1 > R)
    		{
    			R = i + p[i] - 1;
    			pos = i;//更新R以及pos
    		}
    		maxlen = max(maxlen,p[i] - 1);//更新maxlen
    	}
    	return maxlen;
    }
    

    严谨时间复杂度证明: https://segmentfault.com/a/1190000008484167 翻到最下面

    Code

    最后还是贴一下模板完整代码(luogu上的,居然是蓝题......)

    (主要是填补特殊符号那一段代码):

    #include <bits/stdc++.h>
    using namespace std;
    char a[31000005];
    char str[31000005];
    int p[31000005];
    int lena,lenstr;
    void manacher()
    {
    	int R = 0 , pos = 0 ,maxlen = 0;
    	for(int i = 1 ; i <= lenstr ; i ++)//第一个以及最后一个只是为了处理边界,我们枚举的时候没必要枚举那两个,没什么用
    	{
    		if(i <= R)
    			p[i] = min(p[2 * pos - i],R - i + 1);
    		else p[i] = 1;
    		while(str[i - p[i]] == str[i + p[i]])p[i] ++;//暴力枚举
    		if(i + p[i] - 1 > R)
    		{
    			R = i + p[i] - 1;
    			pos = i;//更新R以及pos
    		}
    		maxlen = max(maxlen,p[i] - 1);//更新maxlen
    	}
    	cout << maxlen;
    }
    int main()
    {
    	cin >> a;
    	lena = strlen(a);
    	str[0] = '^';//这个字符随便定
    	for(int i = 1 ; i <= 2*lena + 1; i ++)
    	{
    		if(i % 2 == 1)str[i] = '#';
    		else str[i] = a[i / 2 - 1];
    	}
    	lenstr = 2 * lena + 1;
    	str[lenstr + 1] = '$';
    	//实际上我们用到的是1 到 lena*2+1 这段区间的字符,最前面和最后面的两个是为了处理边界
    	manacher();
    	return 0;
    }
    
  • 相关阅读:
    php ajax 表格排序,调整列宽,修改内容
    Jquery隐藏式评论
    还是一个鼠标点击td变成input,失去焦点更新数据库
    Jquery php 点击td变成input,修改后失去焦点发送数据
    hdu 1713 相遇周期 比较绕的最大公约,最小公倍问题
    hdu 1722 Cake (抽象出欧几里得)
    提供Webservice的地方
    实现汉字转拼音
    java 注解
    c#获取汉字的拼音首字母
  • 原文地址:https://www.cnblogs.com/MYCui/p/13877437.html
Copyright © 2011-2022 走看看