1. 程式人生 > >POJ 3693 Maximum repetition substring(後綴數組+ST表)

POJ 3693 Maximum repetition substring(後綴數組+ST表)

%s 是否 很難 span 順序 template else if lse turn

題意

定義一個字符串的復讀數為它可以被分割成最多的的若幹個相同連續的子串個數,求一個字符串中復讀次數最大的子串位置,若有相同,輸出字典序最小的。

\(1 \leq \text{length}(string)\leq 10^5\)

思路

後綴數組的 \(H\) 是排名相鄰兩個後綴的 \(\text{lcp}\) ,那麽只要對這個數組處理一個 \(\text{ST}\)表,就可以 \(O(1)\) 詢問任兩個後綴的 \(\text{lcp}\) 了。

寫法比較難想到,首先枚舉分成的若幹個相同連續子串的長度 \(l\), 然後把字符串分成 \(\displaystyle{n\over l}\) 份,然後對於第 \(i\)

份和第 \(i+1\) 份的開頭,求一次 \(\text{lcp}\),就可以知道以第 \(i\) 份串開頭的,相同連續子串的長度為 \(l\) 的最大復讀數,當然答案可能不在整的位置上,求出的 \(\text{lcp}\) 可能也不是 \(l\) 的倍數,所以再往回跳到剛好的位置,然後再算一次答案即可。

求字典序最小的可以暴力一點,把復讀數最大的 \(l\) 都存下來,然後按照後綴數組的順序一一檢查是否合法。

這題講起來是很難講清楚,建議閱讀羅穗騫的論文《後綴數組——處理字符串的有力工具》。

代碼

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
typedef long long ll;
const int N=1e5+5;
int sa[N],rk[N],H[N],tmp[3][N];
char s[N];
int ans[N];
int n;

struct SparseTable
{
    int st[N][20],bin[(1<<18)+5];
    void init(int *arr,int n)
    {
        bin[1]=0;FOR(i,2,1<<18)bin[i]=bin[i>>1]+1;
        FOR(i,1,n)st[i][0]=arr[i];
        FOR(j,1,18)
            FOR(i,1,n-(1<<j)+1)
                st[i][j]=std::min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    }
    int query(int l,int r)
    {
        int k=bin[r-l+1];
        return std::min(st[l][k],st[r-(1<<k)+1][k]);
    }
}St;

void get_SA(int m)
{
    memset(tmp,0,sizeof(tmp));
    int *x=tmp[0],*y=tmp[1],*c=tmp[2];
    FOR(i,1,n)c[x[i]=s[i]]++;
    FOR(i,1,m)c[i]+=c[i-1];
    DOR(i,n,1)sa[c[x[i]]--]=i;
    for(int k=1;k<=n;k<<=1)
    {
        int p=0;
        FOR(i,n-k+1,n)y[++p]=i;
        FOR(i,1,n)if(sa[i]>k)y[++p]=sa[i]-k;
        FOR(i,0,m)c[i]=0;
        FOR(i,1,n)c[x[y[i]]]++;
        FOR(i,1,m)c[i]+=c[i-1];
        DOR(i,n,1)sa[c[x[y[i]]]--]=y[i];
        std::swap(x,y);
        p=x[sa[1]]=1;
        FOR(i,2,n)
            x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
        if(p==n)break;
        m=p;
    }
    FOR(i,1,n)rk[sa[i]]=i;
    int k=0;
    FOR(i,1,n)
    {
        if(k)k--;
        if(rk[i]==1)continue;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
        H[rk[i]]=k;
    }
    St.init(H,n);
}

int get_lcp(int x,int y)
{
    if(x==y)return n-x+1;
    x=rk[x],y=rk[y];
    if(x>y)std::swap(x,y);
    return St.query(x+1,y);
}

int main()
{
    int cas=0;
    while(true)
    {
        scanf("%s",s+1);
        if(s[1]=='#')break;
        n=strlen(s+1);
        get_SA(256);
        int rn=1,x=1,y=1;
        ans[0]=0;
        FOR(i,2,n)if(s[i]<s[x])x=y=i;
        FOR(l,1,n)
        {
            for(int i=1,j;(j=i+l)<=n;i+=l)
            {
                int lcp=get_lcp(i,j),cnt=lcp/l;
                if(cnt+1>rn)rn=cnt+1,ans[ans[0]=1]=l;
                else if(cnt+1==rn&&ans[ans[0]]!=l)ans[++ans[0]]=l;
                int a=(i+lcp-1)-(cnt+1)*l+1,b=(j+lcp-1)-(cnt+1)*l+1;
                if(a>=1&&b>=1)
                {
                    lcp=get_lcp(a,b),cnt=lcp/l;
                    if(cnt+1>rn)rn=cnt+1,ans[ans[0]=1]=l;
                    else if(cnt+1==rn&&ans[ans[0]]!=l)ans[++ans[0]]=l;
                }
            }
        }
        bool flg=0;
        FOR(i,1,n)FOR(j,1,ans[0])
        {
            if(flg)break;
            if(get_lcp(sa[i],sa[i]+ans[j])>=(rn-1)*ans[j])
            {
                x=sa[i],y=sa[i]+rn*ans[j]-1;
                flg=1;
            }
        }
        printf("Case %d: ",++cas);
        FOR(i,x,y)putchar(s[i]);
        puts("");
    }
    return 0;
}

POJ 3693 Maximum repetition substring(後綴數組+ST表)