[SHOI2011]双倍回文 题解

Posted jeefy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[SHOI2011]双倍回文 题解相关的知识,希望对你有一定的参考价值。

[SHOI2011]双倍回文 题解

看了一些写回文自动机的大佬的代码,我深感敬畏,于是我转身向Manacher走去

现在荣登最优解第一页……

说实话,这个方法的复杂度是很玄学的,但是加了一些优化之后,就几乎不可能被卡到 \\(O(n^2)\\) 了。

具体思路如下:

预处理字符串部分就略过吧

  • 我们预先跑一次 Manacher 算法,考虑到我们其实只需要偶数的回文串的信息,所以将步长设为 2 就行了。
int M(0), R(0), p;
for (int i(1); i < n; i += 2) 
    p = R > i ? min(R - i + 1, P[(M<<1) - i]) : 1;
    while (s[i + p] == s[i - p]) ++p; // 向两边扩展 
    if (i + p - 1 > R) M = i, R = i + p - 1; // 更新边界
    P[i] = p;        

  • 接着,我们寻找答案。考虑枚举每一个以 i 为中心的偶数回文串。对于这个回文串,我们枚举其回文左区间内的所有点,判断其能否与右区间内对应的区间构成双倍回文即可。换句话来说,假如我们枚举到了 j,那么如果 j 是左侧的合法答案,就一定有 j + P[j] > i

    额,注意一下,这里的 P[i] 指的是回文串的直径,是包括了中间点的元素的。也就是说,回文串所在区间其实是 (i - P[i], i + P[i])(闭区间!)

    但是,这并不是充要条件。我们考虑这样一种情况

    这个时候,j 其实是不合法的,因为绿色的区间才是合法的区间(合法的区间一定是被 i 的回文区间完全包含的!)。所以,对于合法区间的中点,一定在 i 的做右区间中点的左边或者右边。那么实际上,我们只需要枚举 (i, i + P[i]/2] 作为中点即可。

  • 小小的优化:我们需要最大值,所以令答案为 ans,我们枚举 (i + ans, i + P[i]/2] 即可。而且,我们需要的偶数的回文串,所以步长也可以设为 2 来枚举的。

参考代码:

#include <stdio.h>

#define N 1000006

int n, P[N];
char s[N];

inline int min(int x, int y) 
    return x < y ? x : y;

inline int max(int x, int y) 
    return x > y ? x : y;


int main() 
    scanf("%d", &n);

    char tmp;
    do tmp = getchar(); while (tmp < \'a\');
    s[0] = \'^\', s[1] = \'#\', s[2] = tmp, s[3] = \'#\';
    for (int i(2); i <= n; ++i) 
        s[(i<<1)] = getchar(), s[(i<<1)|1] = \'#\';
    

    // printf("%s\\n", s);

    int ans(0);
    int M(0), R(0), p;
    n = (n<<1) + 2;
    for (int i(1); i < n; i += 2) 
        p = R > i ? min(R - i + 1, P[(M<<1) - i]) : 1;
        while (s[i + p] == s[i - p]) ++p; // 向两边扩展 
        if (i + p - 1 > R) M = i, R = i + p - 1; // 更新边界
        P[i] = p;        
    
    // 寻找答案
    for (int i(1); i < n; i += 2) 
        int p = P[i] / 2 + 1;
        for (int r(ans); r < p; r += 2) 
            if (P[i + r] > r && P[i - r] > r) ans = r;
        
    
    printf("%d\\n", ans + ans);
    return 0;

bzoj2342[Shoi2011]双倍回文

这题属于博主还未填坑系列,先嘴巴AC,到时候有时间再搞字符串时,再来好好填坑。

废话不多说上题:

题解:

显然是和马拉车有关的吧,我们可以先对整个串跑一个马拉车,然后枚举‘#’好字符,并以他为中心,在枚举一个在其回纹半径之内的‘#’号,检查二号#是否能覆盖一号,可以的话显然就是一个双回文了,但他的复杂度是n平方的,所以要优化,优化也不难,

思考一下,就会发现,当一号的回文半径很大时,如果二号#不能覆盖一号#,那么当一号#被更新更向右时,显然也是无法覆盖的

所以路径压缩以下,用并查集来实现。

代码:以后填坑。

 

以上是关于[SHOI2011]双倍回文 题解的主要内容,如果未能解决你的问题,请参考以下文章

bzoj2342: [Shoi2011]双倍回文 pam

bzoj2342[Shoi2011]双倍回文

bzoj2342SHOI2011双倍回文

bzoj2342 [Shoi2011]双倍回文 (manacher)

bzoj2342 [Shoi2011]双倍回文

BZOJ 2342: SHOI2011 双倍回文