51 Nod 1154 回文串划分

来源:互联网 发布:mysql 触发器原理 编辑:程序博客网 时间:2024/06/05 07:03
1089 最长回文子串 V2(Manacher算法)
基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题
 收藏
 取消关注
回文串是指aba、abba、cccbccc、aaaa这种左右对称的字符串。
输入一个字符串Str,输出Str里最长回文子串的长度。
Input
输入Str(Str的长度 <= 100000)
Output
输出最长回文子串的长度L。
Input示例
daabaac
Output示例
5
相关问题

想法:

Manacher算法

这个算法的巧妙之处,便是把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。那怎么去实现呢?

我们要求字符串s中最长的回文子串

Manacher重新构造了一个新数组,在每两个字符中间插入一个'#',如下图:

我们很容易发现,如果出现这种情况(如下图)在处理第三个字符('#')市,会发现扫到左侧时候可能会出现越界

为了避免索引数超出数组边界值做字符比较,可以在处理过的字符串的第一个位置(索引为0的位置)加入一个区分字符,笔者喜欢用'@'。

上图便是处理过的样子。(编号表示数组位数,3表示是s[3]的字符)

我们现在用p[i]数组表示以字符s[i]为中心的回文半径(如p[3] = 1)。

我们很容易发现最长子序列的长度刚好是(p[i] - 1)。

那么我们怎么来求p[i]数组呢?

步骤如下:

当处理到[i]的时候,我们去找0-i中是否存在一个数(设为id)使得p[id] + id > i,设mx = p[id] + id。

如上图,由于红的字符串是回文字符串,所以关于j对称的回文子串和关于i对称的回文子串是完全一样的(图中两段绿色的线条),而满足mx-i>P[j]时说明此时j的回文子串半径小于j到mx关于j对称的左端点的差,此时可以初始化P[i]=P[j]。但如果绿色部分超出id的回文串呢?

如下图:

紫色表示以j为中心的回文串超出以id为中心的回文半径的部分(即图中红色部分)由于红的字符串是回文字符串,所以关于j对称的回文子串和关于i对称的在mx和mx的对称点之间的回文子串是完全一样的(图中两段绿色的线条),而满足mx-i<=P[j]时说明此时j的回文子串半径大于或等于j到mx关于j对称的左端点的差,此时可以初始化P[i]=mx-i,超出部分只能一个个判断。再对P[i]的回文子串半径进行进一步的增大。(以上两图均取自参考论文)

综上所述初始化p[i] = min(p[j], mx - i);

对于超出回文字符串部分(即红色部分,我们只能一个一个的去判断)

即while(s[i + p[i]] == s[i - p[i]])p[i] += 1;

当mx <= i 的时候我们只能将p[i] 初始化为1(对于它都是未知的,只能通过一位一位去比较)。

具体代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 220000 + 5;
char s[N];
int p[N];
int n, id, mx;
void work()
{
    id = mx = 0;
    int ans = 0;
    n = strlen(s);
    for(int i = n;i >= 0; i --)
    {
        s[2 * i + 2] = s[i];
        s[2 * i + 1] = '#';
    }
    s[0] = '@';
    p[0] = 1;//p[i]数组表示以字符s[i]为中心的回文半径
    for(int i = 1; i < 2 * n + 2; i ++)
    {
        if(mx > i)
            p[i] = min(p[2 * id - i], mx - i);
        else
            p[i] = 1;
        while(s[i + p[i]] == s[i - p[i]])
            p[i] += 1;
        if(mx < p[i] + i)
        {
            id = i;
            mx = p[i] + i;
        }
        ans = max(ans, p[i] - 1);
    }
    printf("%d\n", ans);
}


int main()
{
    while(scanf("%s", s) == 1)
        work();
    return 0;
}

原创粉丝点击