Ac.Wing 139. 回文子串的最大长度

题解:
这个题目如果单独拿出来就是一道裸的Manacher算法,但是这里希望我们强化一下Hash算法,所以我就写了两个版本的。
Manacher:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<bitset>
#define ll long long
#define INF 1e12
#define inf -1e12
#define PII pair<int,int>
#define ull unsigned long long
#define ios std :: ios :: sync_with_stdio(false)

using namespace std;

const int maxn = 1e6 + 19;

char s[maxn];
char s_new[2 * maxn];
int p[2 * maxn];

int init()
{
    int len = strlen(s);
    s_new[0] = '$';
    s_new[1] = '#';
    int j = 2;
    for(int i = 0;i < len;i++){
        s_new[j++] = s[i];
        s_new[j++] = '#';
    }
    s_new[j] = '\0';
    return j;
}

int manacher()
{
    int len = init();
    int max_len = 1,id,mx = 0;
    for(int i = 1;i <= len;i++){
        if(i < mx) p[i] = min(p[2 * id - i],mx - i);
        else p[i] = 1;
        while(s_new[i - p[i]] == s_new[i + p[i]]) p[i] ++;
        if(mx < i + p[i]){
            id = i;
            mx = i + p[i];
        }
        max_len = max(max_len,p[i] - 1);
    }
    return max_len;
}

int main()
{
    int cnt = 1;
    while(~scanf("%s",s)){
        if(s[0] == 'E') break;
        cout << "Case " << cnt << ": " << manacher() << endl;
        cnt++;
    }
    return 0;
}

Hash:
判断一个字符串是不是回文串,我们如果从Hash值的角度入手的话,我们就需要维护一个前缀后缀的Hash数组,只要把这个字符串一分为二,分别比较其前缀值和后缀值判断是否相等即可。但是这里要注意的是,我们对于奇回文和偶回文的处理是不一样的。
我们假设当前的回文串是一个奇回文,那么肯定他的中心位置是一个字符,我们假设当前的位置是i,那么就有 H ( i − p , i − 1 ) = H ( i + 1 , i + p ) H(i - p,i - 1) = H(i + 1,i + p) H(ip,i1)=H(i+1,i+p)。其中p是回文串可以延申的最大长度,那么最后得到的回文串的长度是: l e n = 2 p + 1 len = 2p + 1 len=2p+1
我们再考虑偶回文的情况,此时他的中心位置就不再是一个具体的字符了,而变成了一个“缝隙”,此时我们考虑缝隙左边的字符,就有: H ( i − p , i ) = H ( i + 1 , i + p + 1 ) H(i - p,i ) = H(i + 1,i + p + 1) H(ip,i)=H(i+1,i+p+1),此时的回文串的长度为: l e n = 2 ( p + 1 ) len = 2(p + 1) len=2(p+1)
现在我们只需要预处理出当前字符串的前缀后缀Hash数组(注意处理后缀的时候尽量不要reverse,这样在后面处理起来会麻烦)然后二分答案,查找p的大小即可。
需要注意的两点:

  1. 对于p的查找千万小心 p = 1 p = 1 p=1的情况,此时对偶回文的查找应该有特判。
  2. 处理后缀和尽量不要reverse,把从小到大换成从大到小即可,但是在求Hash值的时候一定要注意它的变化。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<bitset>
#include<list>
#define ll long long
#define INF 1e12
#define inf -1e12
#define PII pair<int,int>
#define ull unsigned long long
#define ios std :: ios :: sync_with_stdio(false)

using namespace std;

const int maxn = 1e6 + 11;
const int P = 131;

ull p[maxn * 2],H1[maxn],H2[maxn];

void init(string s,int len)
{
    memset(p,0,sizeof(p));
    memset(H1,0,sizeof(H1));
    memset(H2,0,sizeof(H2));
    p[0] = 1;
    H1[0] = s[0] - 'a' + 1;
    for(int i = 1; i <= len; i++) p[i] = p[i - 1] * P;
    for(int i = 1; i < len; i++) H1[i] = P * H1[i - 1] + (s[i] - 'a' + 1);
    H2[len - 1] = s[len - 1] - 'a' + 1;
    for(int i = len - 2;i >= 1;i--) H2[i] = H2[i + 1] * P + (s[i] - 'a' + 1);
}

ull Hash1(int i,int j) //normal
{
    return H1[j] - H1[i - 1] * p[j - i + 1];
}

ull Hash2(int i,int j) //reverse
{
    return H2[i] - H2[j + 1] * p[j - i + 1];
}

int Judge(string s,int len)
{
    int ans = 0;
    for(int i = 0;i < len;i++){
        int l = 0, r = len,mid;
        while(l < r){
            mid = (l + r + 1) >> 1;
            if(i - mid < 0 || mid + i >= len) r = mid - 1;
            else if(Hash1(i - mid,i - 1) == Hash2(i + 1,i + mid)) l = mid;
            else r = mid - 1;
        }
        ans = max(ans,l << 1 | 1);
        l = 0,r = len,mid = 0;
        while(l < r){
            mid = (l + r + 1) >> 1;
            if(i - mid < 0 || mid + i >= len) r = mid - 1;
            else if(Hash1(i - mid,i) == Hash2(i + 1,i + mid + 1)) l = mid;
            else r = mid - 1;
        }
        ans = max(ans,l != 0 ? (l + 1) << 1 : 1);
    }
    return ans;
}

int main()
{
    ios;
    string s;
    int cnt = 0;
    while(cin >> s){
        if(s == "END") break;
        int len = s.length();
        init(s,len);
        int ans = Judge(s,len);
        cout << "Case " << ++ cnt << ": " << ans << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CUCKyrie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值