最长公共回文子串 (manacher+hash+二分)

 

求两个字符串(记为s1,s2)的最长公共回文子串

 

解法:先用mnanacher算法O(n)处理出 s1串的最长回文子串长度 L,那么最后的答案肯定是L,L-2,L-4 ~ 0,并且我们已经求出了 p[i] 数组(以i为中心的最长回文半径),用于后面二分答案的判断。

这里有个坑点:二分时奇偶回文要分开二分。因为如何有2k+2长度的公共回文。肯定也会有2k长度的公共回文,但是不保证也有2k+1的长度的公共回文。。. 如果不想奇偶分开讨论的话,可以用马拉车的技巧,转化为全部都是奇回文。

 

/* ***********************************************
Author        :pall_scall
Created Time  :2019年04月08日 星期一 11时53分02秒
File Name     :vj.cpp
************************************************ */  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

typedef unsigned long long ull;
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&-x
const int maxn = 2e6 + 5;
const int mod = 1e9 + 7;
const double eps = 1e-6;
const double pi = acos(-1.0);
char s[maxn<<1],ss[maxn<<1],s1[maxn],s2[maxn],str[maxn<<1];
int p[maxn<<1];
ull hash1[maxn<<1],hash2[maxn<<1],po[maxn<<1];
int len1,len2;
ull geth1(int l,int r){
    if(l == 0) return hash1[r];
    return (ull)(hash1[r]-hash1[l-1]*po[r-l+1]);
}
ull geth2(int l,int r){
    if(l == 0) return hash2[r];
    return (ull)(hash2[r]-hash2[l-1]*po[r-l+1]);
}
int manacher(int len){
    int id = 0,mx = 0;
    int res = 0;
    for(int i = 2; i < len; i++){
        p[i] = mx>i?min(p[2*id-i],mx-i):1;
        while(str[i+p[i]] == str[i-p[i]]) p[i]++;
        if(i+p[i] > mx){
            mx = i+p[i];
            id = i;
        }
        res = max(res,p[i]);
        //printf("%d ",p[i]);
    }
    //cout<mp;
bool judge(int len,int sz){
    mp.clear();
    for(int i = 2; i+len-1 < sz; i+=2){ //枚举回文中心
        if(p[i]-1>=len){ //以i为中心的回文子串长度大于二分的长度
            int ra = len/2;  
            int id = i/2-1; //对应回原串回文中心
            mp[geth1(id-ra,id+ra)] = 1;
            //cout<<1;
        }
    }
    for(int i = 0; i+len-1 < len2; i++){
        if(mp.count(geth2(i,i+len-1))) return true;
    }
    return false;
}
int main(){
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    po[0] = 1;
    for(int i = 1; i < maxn*2; i++){
        po[i] = po[i-1]*31;
    }
    while(~scanf("%s%s",s1,s2)){
        /*预处理将字符串中的回文串全部变成奇回文*/
        len1 = len2 = 0;
        s[len1++] = 'A';
        for(int i = 0; s1[i]!='\0'; i++){
            s[len1++] = s1[i];
            s[len1++] = 'A';
        }
        ss[len2++] = 'A';
        for(int i = 0; s2[i]!='\0'; i++){
            ss[len2++] = s2[i];
            ss[len2++] = 'A';
        }
        /*...........................*/
        /*manacher的预处理*/
        int tot = 0;
        str[tot++] = '$';
        str[tot++] = '#';
        for(int i = 0; i < len1; i++){
            str[tot++] = s[i];
            str[tot++] = '#';
        }
        str[tot] = '\0';
        /*...................*/
        /*hash处理*/
        hash1[0] = s[0];
        for(int i = 1; i < len1; i++)
            hash1[i] = hash1[i-1]*31+s[i];
        hash2[0] = ss[0];
        for(int i = 1; i < len2; i++)
            hash2[i] = hash2[i-1]*31+ss[i];
        /*..........*/
        int l = 0, r = manacher(tot)/2;  
        /*由于之前的奇回文处理,现在的r是s串的(最长回文串长度-1)/2,其实就是原串的最长回文串长度,因为s串加倍处理过*/
        int ans = 0;
        while(l <= r){
            int mid = (l+r)>>1; //二分答案长度
            if(judge(mid<<1|1,tot)){ 
                l = mid+1;
                ans = mid*2+1;
            }else
                r = mid-1;
        }
        printf("%d\n",ans/2);
    }
    return 0;
}

 

你可能感兴趣的:(字符串)