Mediocre String Problem (2018南京M,回文+LCP 3×3=9种做法 %%%千年好题 感谢"Grunt"大佬的细心讲解)


layout: post
title: Mediocre String Problem (2018南京M,回文+LCP 3×3=9种做法 %%%千年好题 感谢"Grunt"大佬的细心讲解)
author: "luowentaoaa"
catalog: true
mathjax: true
tags:
- 回文树
- 马拉车
- 扩展KMP
- 后缀数组
- 后缀自动机
- 字符串哈希


题意

给出一个串S,和一个串T.

要求 从S串中取一个子串,后面接上T串的一个前缀 组成一个结果串,(要求S串的部分比T串的部分长)

其中,S串贡献的部分 可以分成两部分,S1+S2;

前面的S1 是T部分的反转;

S2 就只能是回文串,因为S串的部分必须比T的多,所以S2长度必须大于等于1

然后我们可以分成两部分,首先先把S中的所有回文串求出,可以用(回文树/马拉车/字符串哈希)

对于每一个回文串,它的左边半径部分都可以作为S1的右端点,除了中心,而且边缘也可以吃到一个

比如 CABABA 其中 回文串中心是第二个A,S1的右端点可以是CAB 注意C也可以的哦

然后找出这个端点剩下的就是求S1和T的lcp的长度了,根据题意每一个长度都一个贡献一个符合要求的答案

针对这个问题 我们可以把S 反转 和T用EXKMP 求lcp (也可以用后缀数组/字符串哈希/exkmp/后缀自动机)

所以 这题的解法大概有(3×3=9 或者3×4=12)种。

做法1 :马拉车+EXKMP

///感谢Grunt 大佬的细心讲解
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
char s[maxn],t[maxn];
int lens,lent;
int mynext[maxn];
int extend[maxn];
ll sum[maxn];
void pre_EKMP(char x[],int m,int next[]){
    next[0]=m;
    int j=0;
    while(j+1<m&&x[j]==x[j+1])j++;
    next[1]=j;
    int k=1;
    for(int i=2;i<m;i++){
        int p=next[k]+k-1;
        int L=next[i-k];
        if(i+L<p+1)next[i]=L;
        else{
            j=max(0,p-i+1);
            while(i+j<m&&x[i+j]==x[j])j++;
            next[i]=j;
            k=i;
        }
    }
}
void EKMP(char x[],int m,char y[],int n,int next[],int extend[]){
    pre_EKMP(x,m,next);
    int j=0;
    while(j<n&&j<m&&x[j]==y[j])j++;
    extend[0]=j;
    int k=0;
    for(int i=1;i<n;i++){
        int p=extend[k]+k-1;
        int L=next[i-k];
        if(i+L<p+1)extend[i]=L;
        else{
            j=max(0,p-i+1);
            while(i+j<n&&j<m&&y[i+j]==x[j])j++;
            extend[i]=j;
            k=i;
        }
    }
}
char Ma[maxn*2];
int Mp[maxn*2];
void Manacher(char s[],int len){
    int l=0;
    Ma[l++]='$';
    Ma[l++]='#';
    for(int i=0;i<len;i++){
        Ma[l++]=s[i];
        Ma[l++]='#';
    }
    Ma[l]=0;
    int mx=0,id=0;
    for(int i=0;i<l;i++){
        Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
        while(Ma[i+Mp[i]]==Ma[i-Mp[i]])Mp[i]++;
        if(i+Mp[i]>mx){
            mx=i+Mp[i];
            id=i;
        }
    }
}
ll getsum(int l,int r){
    if(l>r)return 0;
    else if(l<=0)return sum[r];
    else return sum[r]-sum[l-1];
}
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    cin>>s>>t;
    lens=strlen(s);lent=strlen(t);
    Manacher(s,lens);
    reverse(s,s+lens);
    EKMP(t,lent,s,lens,mynext,extend);
    reverse(extend,extend+lens);
    sum[0]=extend[0];
    for(int i=1;i<lens;i++){
        sum[i]=sum[i-1]+extend[i];
    }
    ll ans=0;                                     ///0 1 2 3 4
    for(int i=2;i<2*lens+3;i++){                  ///a b a b a
        int cnt=Mp[i]-1;      ///6-1=5;            $ # a # b # a # b # a #
        if(cnt==0||Mp[i]==0)continue;           ///0 1 2 3 4 5 6 7 8 9 10 11
        if(cnt&1){///奇数长度的回文 例如ababa  MP= 1 1 2 1 4 1 6 1 4 1  2  1  0
            int where=(i-2)/2;  ///找到a这个位置 =(6-1)/2=2;
            int r=where-1;      ///然后从a前面一个位置b作为右端点 =1
            int l=where-Mp[i]/2; ///然后找到左端点=2-6/2 =-1
            ans+=getsum(l,r);
        }
        else{                 /// 偶数的  例如aabbaa
            int where=(i-2-1)/2;   /// 找到b#b中间的‘#’ 左边的b
            int r=where-1;         ///右端点
            int l=where-cnt/2;    /// 左端点
            ans+=getsum(l,r);
        }
    }
    cout<<ans<<endl;
    return 0;
}

做法2. 回文自动机+EXKMP

附上大佬的题解:

M.Mediocre String Problem

题意:给S串与T串。S[i..j]+T[1..k]为回文串,且|S[i..j]|>|T[1..k]|,求(i,j,k)个数。

将S[i..j]分为两个部分,S[i..p]为T[1..k]的反转,S[p+1..j]为回文串。

由于|S[i..j]|>|T[1..k]|,所以S[p+1..j]必须不为空。

枚举回文串的起始点p+1,那么我们要求的是:

1.由于S[i..p]为T[1..k]的反转,我们只要求有多少个(i,k)。这个部分是exkmp的基础。

将S反转,跟T跑exkmp,求出ex[],再把ex[]反过来即可。

2.S[p+1..j]为回文串,我们只要求有多少个j,即求的是以p+1为起点的回文串个数cnt[]。

那么只要把S串倒着插入回文自动机,cnt[i]=插入第i个字符后,fail树的深度。

最后枚举回文串起点p+1,算出ex[p]*cnt[p+1],求和即为答案。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
char s[maxn],t[maxn];
int lens,lent;
int mynext[maxn];
int extend[maxn];
int num[maxn];
void pre_EKMP(char x[],int m,int next[]){
    next[0]=m;
    int j=0;
    while(j+1<m&&x[j]==x[j+1])j++;
    next[1]=j;
    int k=1;
    for(int i=2;i<m;i++){
        int p=next[k]+k-1;
        int L=next[i-k];
        if(i+L<p+1)next[i]=L;
        else{
            j=max(0,p-i+1);
            while(i+j<m&&x[i+j]==x[j])j++;
            next[i]=j;
            k=i;
        }
    }
}
void EKMP(char x[],int m,char y[],int n,int next[],int extend[]){
    pre_EKMP(x,m,next);
    int j=0;
    while(j<n&&j<m&&x[j]==y[j])j++;
    extend[0]=j;
    int k=0;
    for(int i=1;i<n;i++){
        int p=extend[k]+k-1;
        int L=next[i-k];
        if(i+L<p+1)extend[i]=L;
        else{
            j=max(0,p-i+1);
            while(i+j<n&&j<m&&y[i+j]==x[j])j++;
            extend[i]=j;
            k=i;
        }
    }
}
struct Palindromic_Tree{
    int next[maxn][26];
    int fail[maxn];
    int cnt[maxn];
    int num[maxn];
    int len[maxn];
    int S[maxn];
    int last;
    int n;
    int p;
    int newnode(int l){
        for(int i=0;i<26;i++)next[p][i]=0;
        cnt[p]=num[p]=0;len[p]=l;
        return p++;
    }
    void init(){
        p=0;
        newnode(0);
        newnode(-1);
        last=n=0;
        S[n]=-1;
        fail[0]=1;
    }
    int getfail(int x){
        while(S[n-len[x]-1]!=S[n])x=fail[x];
        return x;
    }
    int add(int c){
        c-='a';
        S[++n]=c;
        int cur=getfail(last);
        if(!next[cur][c]){
            int now=newnode(len[cur]+2);
            fail[now]=next[getfail(fail[cur])][c];
            next[cur][c]=now;
            num[now]=num[fail[now]]+1;
        }
        last=next[cur][c];
        cnt[last]++;
        return num[last];
    }
    void count(){
        for(int i=p-1;i>=0;i--)cnt[fail[i]]+=cnt[i];
    }
}pam;
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    cin>>s>>t;
    lens=strlen(s);lent=strlen(t);
    pam.init();
    for(int i=lens-1;i>=0;i--){
        num[i]=pam.add(s[i]);
    }
    reverse(s,s+lens);
    EKMP(t,lent,s,lens,mynext,extend);
    reverse(extend,extend+lens);
    ll ans=0;
    for(int i=0;i<lens-1;i++){
        ans+=1LL*extend[i]*num[i+1];
    }
    cout<<ans<<endl;
    return 0;
}

做法3.后缀数组+回文自动机(498ms)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct DA{
    #define F(x) ((x)/3+((x)%3==1?0:tb))
    #define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
    int sa[maxn*20],rank[maxn*20],height[maxn*20],str[maxn*20];
    int wa[maxn*20],wb[maxn*20],wv[maxn*20],wss[maxn*20];
    int c0(int *r,int a,int b){
        return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];
    }
    int c12(int k,int *r,int a,int b){
        if(k==2)
            return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
        else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
    }
    void sort(int *r,int *a,int *b,int n,int m){
        int i;
        for(i=0;i<n;i++)wv[i]=r[a[i]];
        for(i=0;i<m;i++)wss[i]=0;
        for(i=0;i<n;i++)wss[wv[i]]++;
        for(i=1;i<m;i++)wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--)
            b[--wss[wv[i]]]=a[i];
    }
    void dc3(int *r,int *sa,int n,int m){
        int i,j,*rn=r+n;
        int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
        r[n]=r[n+1]=0;
        for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=i;
        sort(r+2,wa,wb,tbc,m);
        sort(r+1,wb,wa,tbc,m);
        sort(r,wa,wb,tbc,m);
        for(p=1,rn[F(wb[0])]=0,i=1;i<tbc;i++)
            rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
        if(p<tbc)dc3(rn,san,tbc,p);
        else for(i=0;i<tbc;i++)san[rn[i]]=i;
        for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
        if(n%3==1)wb[ta++]=n-1;
        sort(r,wb,wa,ta,m);
        for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
        for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
            sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
        for(;i<ta;p++)sa[p]=wa[i++];
        for(;j<tbc;p++)sa[p]=wb[j++];
    }
    void da(int n,int m){
        for(int i=n;i<n*3;i++)str[i]=0;
        dc3(str,sa,n+1,m);
        int i,j,k=0;
        for(i=0;i<=n;i++)rank[sa[i]]=i;
        for(i=0;i<n;i++){
            if(k)k--;
            j=sa[rank[i]-1];
            while(str[i+k]==str[j+k])k++;
            height[rank[i]]=k;
        }
    }
    void print(int n){
        cout<<"sa[] ";
        for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
        cout<<"rank[] ";
        for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
        cout<<"height[] ";
        for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
    }
}DA;
struct PalTree{
    int next[maxn][26],fail[maxn],cnt[maxn],num[maxn],len[maxn],S[maxn],last,n,p;
    int newnode(int l){
        for(int i=0;i<26;i++)next[p][i]=0;
        cnt[p]=num[p]=0;len[p]=l;return p++;
    }
    void init(){
        p=0;newnode(0);newnode(-1);last=0;n=0;S[n]=-1;fail[0]=1;
    }
    int get_fail(int x){
        while(S[n-len[x]-1]!=S[n])x=fail[x];return x;
    }
    int add(int c){
        c-='a';S[++n]=c;int cur=get_fail(last);
        if(!next[cur][c]){
            int now=newnode(len[cur]+2);
            fail[now]=next[get_fail(fail[cur])][c];
            next[cur][c]=now;num[now]=num[fail[now]]+1;
        }
        last=next[cur][c];cnt[last]++;return num[last];
    }
    void count(){for(int i=p-1;i>=0;i--)cnt[fail[i]]+=cnt[i];}
}PAM;
char s[maxn],t[maxn];
int num[maxn],cnt[maxn];
int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);
    cin>>s>>t;
    int lens=strlen(s),lent=strlen(t);
    int len=0;PAM.init();
    for(int i=0;i<lens;i++)DA.str[len++]=s[lens-i-1]-'a'+1,cnt[lens-i-1]=PAM.add(s[lens-i-1]);
    DA.str[len++]=30;
    for(int i=0;i<lent;i++)DA.str[len++]=t[i]-'a'+1;
    DA.str[len]=0;
    DA.da(len,200);
	int p=DA.rank[lens+1];
	//DA.print(len);
	int now=len+1;
	for(int i=p-1;i>=0;i--){
		now=min(now,DA.height[i+1]);
		if(DA.sa[i]>=0&&DA.sa[i]<lens){
			num[lens-1-DA.sa[i]]=now;
		}
	}
	now=len+1;
	for(int i=p+1;i<=len;i++){
		now=min(now,DA.height[i]);
		if(DA.sa[i]>=0&&DA.sa[i]<lens){
			num[lens-1-DA.sa[i]]=now;
		}
	}
	ll ans=0;
    for(int i=0;i<lens-1;i++){
        ans+=1LL*num[i]*cnt[i+1];
    }
    cout<<ans<<endl;
    return 0;
}

感谢Grunt 大佬的细心讲解!!

///感谢Grunt 大佬的细心讲解

!(Mediocre String Problem/a.jpg)
毅种循环~
!(Mediocre String Problem/b.jpg)

原文地址:https://www.cnblogs.com/luowentao/p/10332309.html