Manacher算法的应用

此生再无相见时 提交于 2020-01-27 18:52:33

Manacher算法是大家常用的,用来求回文串系列问题的算法
具体算法过程,我不多说,不知道的,可以看:Manacher算法详述

通过Manacher算法之后,我们会得到几个比较重要的信息:
首先,就是这个字符串有无回文子串,以及最长的回文子串的长度。
其次,就是以某一点为中心的回文半径,也就是 p 数组。
出题人比较喜欢在这个上面做文章

题型一:判断回文串,以及输出长度

首先是最基本的判断这个字符串是否有回文子串,以及输出最长的回文子串的长度。
模板题目,一般不直接考察
就直接 Manahce算法 就行了
例题:最长回文

char s[maxn],str[maxn];
int p[maxn];
int Init()
{
    int len = strlen(s);
    str[0] = '@',str[1] = '#';
    int j = 2;
    for(int i=0;i<len;++i){
        str[j++] = s[i];
        str[j++] = '#';
    }
    str[j] = 0;
    return j;
}
int manacher()
{
    int mx,id;
    mx = id = 0;
    int ans = -1;
    int len = Init();

    for(int i=1;i<len;i++){
        if(i<mx) p[i] = min(p[id*2-i],mx-i);
        else p[i] = 1;
        while(str[i+p[i]]==str[i-p[i]]) p[i]++;
        if(p[i]+i>mx){
            mx = p[i] + i;
            id = i;
        }
        ans = max(p[i]-1,ans);
    }
    return ans;
}

然后
既然出题人不想直接考察你的模板背的熟不熟
可以在这个回文串上做一下“手脚”……
就比如假设这是一个数字串,那么它可以给所求回文串加一些“限制”
什么要求从回文串开头到中心是递减趋势的,或者递增趋势的
以及非递增趋势,或者非递减趋势

试图在这上面难为你,
其实这也是难为不到我们的,如果对 Manacher算法 了解的比较透彻的的话。
它判断是否是回文串,以及求解回文半径主要体现在那个 while 循环了,
只要我们稍加修改就可以了

while(str[i+p[i]]==str[i-p[i]]) p[i]++;

这是最 low 的回文串半径的求解,意思就是 以 i 为中心的回文串的半径是 p[i],那么以 i 为中心的回文串长度就是 p[i]-1。

我们知道 Manacher算法的第一步就是处理字符串,插入无关字符,以确定对称抽。
那么如果这个回文串的左半部分是递减的话,也就是:5 & 4 & 3 & 3 & 4 & 5 这种格式
很明显就是 左开头的数字 要大于 左开头+2位置的数字,依次类推
也就是 str[i-p[i]]>str[i-p[i]+2]
也就是在那个 while 循环里加一个限制

while(str[i+p[i]]==str[i-p[i]] && str[i-p[i]]>str[i-p[i]+2]) p[i]++;

这样就能得到所要求的回文子串的最长长度了

例题:吉哥系列故事——完美队形II
裸题,稍微一改,即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#include<queue>
#include<utility>
#include<set>
#include<stack>
#include<string>
#include<vector>
#define ll long long
#define llu unsigned long long
using namespace std;
const int maxn = 22000010;
int s[maxn],str[maxn];
int p[maxn];
int Init(int n)
{
    int len = n;
    str[0] = -1,str[1] = 10;
    int j = 2;
    for(int i=0;i<len;++i){
        str[j++] = s[i];
        str[j++] = 10;
    }
    str[j] = 0;
    return j;
}
int manacher(int n)
{
    int mx,id;
    mx = id = 0;
    int ans = 1;
    int len = Init(n);

    for(int i=1;i<len;i++){
        if(i<mx) p[i] = min(p[id*2-i],mx-i);
        else p[i] = 1;
        while(str[i+p[i]]==str[i-p[i]]&&str[i-p[i]]<=str[i-p[i]+2]) p[i]++;
        if(p[i]+i>mx){
            mx = p[i] + i;
            id = i;
        }
        ans = max(p[i]-1,ans);
    }
    return ans;
}
int main(void)
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            scanf("%d",&s[i]);
        }
        printf("%d\n",manacher(n));
    }
    return 0;
}

题型二:考察你对于 数组p 的掌握

数组 p 是用来记录回文半径的,p[i]是代表处理后的字符串,以 i 点为中心的回文半径,那么显然 p[i]-1 就是原字符串中以 i 为中心的回文串长度。

出题人可能会:
字符串 s,输出 s 中最长的两个连续回文子串的长度(例如 abccbaabcac 答案是 9)

他让你找连续两段,而不是单纯的一段回文子串
按照 Manacher算法的传统步骤,
首先我们处理字符串:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# * a * b * c * c * b * a * b * c * a * c *

假设 ans 是前一段回文串的长度,那么 ans/2*3 就是最终答案

我们从 i=1,开始依次 +=2 ,直到字符串结束,
假设 以 i 为中心的回文串的长度是 p[i],那么如果 [i,i+p[i]-1] 这一段内存在点 j,p[j]>=j-i+1,则说明存在题目要求的连续两段回文子串,那么 ans = max(ans,j-i)。

为什么这么说呢

首先 我们可以确定 [i-p[i]+1,i] 和 [i,i+p[i]-1] 这一段是对称的
那么 如果说在 [i,i+p[i]-1] 这一段内存在点 j,使得 [i,j] 和 [j,i+p[j]-1] 对称的话
我们就得到了连续两段回文串,长度是 (j-i)/2*3。

int ans = 0;
for(int i=1;i<n;i+=2){
	for(int j=i+p[i]-1;j-i>ans;j-=2){	//因为找最大ans,所以就从右往左推
		if(p[j]>=j-i+1&&ans<j-i){
			ans = j-i;
			break;
		}
	}
}

例题:Hotaru’s problem
Problem Description

Hotaru Ichijou recently is addicated to math problems. Now she is playing with N-sequence.
Let’s define N-sequence, which is composed with three parts and satisfied with the following condition:

  1. the first part is the same as the thrid part,
  2. the first part and the second part are symmetrical.
    for example, the sequence 2,3,4,4,3,2,2,3,4 is a N-sequence, which the first part 2,3,4 is the same as the thrid part 2,3,4, the first part 2,3,4 and the second part 4,3,2 are symmetrical.

Give you n positive intergers, your task is to find the largest continuous sub-sequence, which is N-sequence.

Input

There are multiple test cases. The first line of input contains an integer T(T<=20), indicating the number of test cases.

For each test case:

the first line of input contains a positive integer N(1<=N<=100000), the length of a given sequence

the second line includes N non-negative integers ,each interger is no larger than 109 , descripting a sequence.

Output

Each case contains only one line. Each line should start with “Case #i: ”,with i implying the case number, followed by a integer, the largest length of N-sequence.

We guarantee that the sum of all answers is less than 800000.

Sample Input

1
10
2 3 4 4 3 2 2 3 4 4

Sample Output

Case #1: 9

AC代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<map>
#include<queue>
#include<utility>
#include<set>
#include<stack>
#include<string>
#include<vector>
#define ll long long
#define llu unsigned long long
using namespace std;
const int maxn = 210000;
int s[maxn],str[maxn],n;
int p[maxn];
int Init()
{
    str[0] = -1,str[1] = -2;
    int j = 2;
    for(int i=0;i<n;++i){
        str[j++] = s[i];
        str[j++] = -2;
    }
    str[j] = 0;
    return j;
}
void manacher()
{
    int mx,id;
    mx = id = 0;
    int len = Init();
    n = len;
    for(int i=1;i<len;i++){
        if(i<mx) p[i] = min(p[id*2-i],mx-i);
        else p[i] = 1;
        while(str[i+p[i]]==str[i-p[i]]) p[i]++;
        if(p[i]+i>mx){
            mx = p[i] + i;
            id = i;
        }
    }
}
int main(void)
{
    int t;
    scanf("%d",&t);
    for(int cas=1;cas<=t;cas++){
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            scanf("%d",&s[i]);
        }
        manacher();
        int ans = 0;
        for(int i=1;i<n;i+=2){
            for(int j=i+p[i]-1;j-i>ans;j-=2){
                if(p[j]>=j-i+1&&ans<j-i){
                    ans = j-i;
                    break;
                }
            }
        }
        printf("Case #%d: %d\n",cas,ans/2*3);
    }
    return 0;
}

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!