1. 程式人生 > >Mediocre String Problem (2018南京M,回文+LCP 3×3=9種做法 %%%千年好題 感謝"Grunt"大佬的細心講解)

Mediocre String Problem (2018南京M,回文+LCP 3×3=9種做法 %%%千年好題 感謝"Grunt"大佬的細心講解)

tag 等於 += max bit xtend 不為 ioc run


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)

Mediocre String Problem (2018南京M,回文+LCP 3×3=9種做法 %%%千年好題 感謝"Grunt"大佬的細心講解)