BZOJ 2565 最长双回文串 Manacher

题目大意:

定义双回文串G是指一个可以被拆分成两个部分(S和T)的字符串G = S + T, 且S和T都是回文串的串, G自己本身可以不是回文串

给出一个长度为n ( 2 <= n <= 10^5 )的字符串, 求其最长双回文子串的长度


大致思路:

首先可以相当manacher算法当中有这样一个过程, 用两个辅助变量mx和p分别表示已有回文半径覆盖到的最右边界和对应中心的位置, 然后在求解过程中更新mx和p, 考虑到S和T的中间字符作为分隔的位置, 当这个位置被mx覆盖之后, mx从左往右更新第一次覆盖的时候p(覆盖它的串的中心)的位置一定是离这个位置最远的也就是找到了S的中心, 由于mx移动距离至多为n, 所以随着manacher算法的进行用dp来记录mx被更新时的那个回文串的信息即可, 找到S中心之后, 类似的反向从右往左做一遍manacher然后找到T的中心

这样O(n)的预处理之后找出了每个分隔符之下S和T的中心的最佳位置, 最后枚举一下分隔符的位置即可找到最优解

总体时间复杂度O(n)

manacher第一次一发AC, 果然还是要想清楚了敲更稳妥一些


代码如下:

Result  :  Accepted     Memory  :  4008 KB     Time  :  88 ms

/**************************************************************
    Problem: 2565
    User: Gatevin
    Language: C++
    Result: Accepted
    Time:88 ms
    Memory:4008 kb
****************************************************************/
 
/*
 * Author: Gatevin
 * Created Time:  2015/3/20 21:27:49
 * File Name: Chitoge_Kirisaki.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;
 
#define maxn 200020
 
char in[maxn], s[maxn]; 
int R[maxn];
int dpl[maxn], dpr[maxn];
 
/*
 * 用dpl[i]表示第i位的位置作为两个回文串的分割字符时候
 * 前面的回文串的中心在dpl[i]位置时最长
 * 用dpr[i]表示第i位的位置作为两个回文串的分割字符的时候
 * 后面的回文串的中心在dp[r]位置时最长
 */
void Manacher(char *s, int *R, int n)
{
    int p = 0, mx = 0;
    R[0] = 1;
    memset(dpl, 0, sizeof(dpl));
    memset(dpr, 0, sizeof(dpr));
    for(int i = 1; i <= n; i++)
    {
        if(mx > i)
            R[i] = min(R[2*p - i], mx - i);
        else R[i] = 1;
        while(s[i - R[i]] == s[i + R[i]])
            R[i]++;
        if(i + R[i] > mx)
        {
            //第一次mx更新的时候更新状态一定是最优的, i单调递增
            for(int j = mx + 1; j <= i + R[i]; j++)
                dpl[j] = i;//由于mx的移动距离最多是n, 复杂度是O(n)
            mx = i + R[i], p = i;
        }
    }
    p = mx = n + 1;
    R[n + 1] = 1;//反过来从右边往左边再做一遍就行了
    for(int i = n; i >= 1; i--)
    {
        if(mx < i)
            R[i] = min(R[2*p - i], i - mx);
        else R[i] = 1;
        while(s[i - R[i]] == s[i + R[i]])
            R[i]++;
        if(i - R[i] < mx)
        {
            for(int j = mx - 1; j >= i - R[i]; j--)
                dpr[j] = i;
            mx = i - R[i], p = i;
        }
    }
    return;
}
 
int main()
{
    scanf("%s", in);
    int n = strlen(in);
    s[0] = '@';
    for(int i = 0; i < n; i++)
        s[2*i + 1] = in[i], s[2*i + 2] = '#';
    s[2*n] = '$', s[2*n + 1] = '\0';
    Manacher(s, R, 2*n);
    int ans = 0;
    for(int i = 2; i < 2*n; i += 2)//分隔符是插入的'#'字符
    {
        int len = (2*i - 2*dpl[i] + 1) / 2;
        len += (2*dpr[i] - 2*i + 1) / 2;
        ans = max(ans, len);
    }
    printf("%d\n", ans);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值