回文子串的最大长度(字符串哈希+二分)

题目链接:https://www.acwing.com/problem/content/description/141/

题目
如果一个字符串正着读和倒着读是一样的,则称它是回文的。

给定一个长度为 N N N 的字符串 S S S,求他的最长回文子串的长度是多少。

输入格式
输入将包含最多 30 30 30 个测试用例,每个测试用例占一行,以最多 1000000 1000000 1000000 个小写字符的形式给出。

输入以一个以字符串 END 开头的行表示输入终止。

输出格式
对于输入中的每个测试用例,输出测试用例编号和最大回文子串的长度(参考样例格式)。

每个输出占一行。

输入样例:

abcbabcbabcba
abacacbaaaab
END

输出样例:

Case 1: 13
Case 2: 6
思路:

字符串哈希+二分 ( N l o g N NlogN NlogN)

为什么可以二分?
因为求最大半径最有单调性,即大的半径是回文串则小的半径一定是回文串
所以这里是前半段满足条件,后半段不满足,用二分模板 2 2 2

字符串预处理:
h l [ ] hl[] hl[]表示字符串正序的哈希值、 h r [ ] hr[] hr[]表示字符串后序的哈希值

将回文串分为两大类:

  1. 长度为奇数
  2. 长度为偶数

奇数的情况:
枚举每个字母作为中心点,二分求半径(左右两边的长度),判断哈希值是否一样,一样就扩大,不一样就缩小

偶数的情况:
先在原始字符串每两个字母中间添加一个没有出现过的字符,就可以将这种情况转化为奇数的情况(中心点可以是加入的字符)

为什么会变成奇数的情况:
假设原来字符串的长度为S,在每两个字符中间补上的字符数量为S-1,所以字符串长度为2S-1

找到半径,怎么求不包含添加字符的长度?
看当前字符串左右两边是不是字母
是,说明字母多一个,字符串长度应为 2 ∗ S + 1 2 \frac{2*S+1}{2} 22S+1 上取整,即 S + 1 S+1 S+1
不是,说明添加的字符多一个 字符串长度应为 2 ∗ S + 1 2 \frac{2*S+1}{2} 22S+1 下取整,即 S S S

get的下标怎么求?
左边(顺序)的直接求 g e t ( i − m i d , i − 1 ) get(i - mid, i - 1) get(imid,i1)
右边(逆序)的应该是 g e t ( i + 1 , i + m i d ) get(i + 1, i + mid) get(i+1,i+mid)
但是右边的应该对应到逆序数组里的下标
逆序数组里 1 1 1 对应的是 n n n 2 2 2 对应的是 n − 1 n-1 n1 3 3 3 对应的是 n − 2 n-2 n2
x x x 对应的就是 n − x + 1 n-x+1 nx+1
所以应该是 g e t ( n − ( i + 1 ) + 1 , n − ( i + m i d ) + 1 ) get(n - (i + 1) + 1, n - (i + mid) + 1) get(n(i+1)+1,n(i+mid)+1)
因为逆序,应该要交换顺序,小的在前大的在后
所以右边的最终应该是 g e t ( n − ( i + m i d ) + 1 , n − ( i + 1 ) + 1 ) get(n - (i + mid) + 1, n - (i + 1) + 1) get(n(i+mid)+1,n(i+1)+1)

AC代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef unsigned long long ULL;

const int N = 2000010,P = 131;

char str[N];
ULL hl[N],hr[N],p[N];

ULL get(ULL h[],int l,int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

int main()
{
    int T = 1;
    while(scanf("%s",str+1),strcmp(str+1,"END")) // 逗号表达式,返回最后一个表达式的值
    {
        int n = strlen(str+1);
        for(int i = n * 2; i; i -= 2) // 在每两个字母中间添加没出现过的字符
        {
            str[i] = str[i / 2];
            str[i-1] = 'z' + 1;
        }
        n *= 2;
        p[0] = 1;
        for(int i = 1,j = n; i <= n; i++,j--) // 字符串预处理
        {
            hl[i] = hl[i-1] * P + str[i] - 'a' + 1;
            hr[i] = hr[i-1] * P + str[j] - 'a' + 1;
            p[i] = p[i-1] * P;
        }
        int ans = 0;
        for(int i = 1; i <= n; i++)
        {
            int l = 0,r = min(i - 1,n - i);
            while(l < r) // 二分半径
            {
                int mid = l + r + 1 >> 1;
                if(get(hl,i - mid,i - 1) == get(hr,n - (i + mid) + 1,n - (i + 1) + 1)) l = mid; // 注意逆序的下标
                else r = mid - 1;
            }
            if(str[i - l] <= 'z') ans = max(ans,l + 1); // 字母多一个,上取整
            else ans = max(ans,l); // 字符多一个,下取整
        }
        printf("Case %d: %d\n",T++,ans);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值