洛谷P2852 - [USACO06DEC]牛奶模式Milk Patterns

Portal

Description

给出一个\(n(n\leq2\times10^4)\)个数的序列,求最长的出现了\(k\)次的子串的长度。

Solution

后缀数组+二分答案。
二分子串长度\(len\),若\(h[x..y]\geq len\)则说明有一个长度为\(len\)的子串,分别在\(sa[x-1..y]\)出现过共\(y-x+2\)次。所以只要找出最长的\(h\geq len\)的区间,对于每个\(len\)从头到尾扫一遍即可找出这个区间。

时间复杂度\(O(nlogn)\)

Code

//[USACO06DEC]牛奶模式Milk Patterns
#include <cstdio>
#include <cstring>
inline char gc()
{
    static char now[1<<16],*s,*t;
    if(s==t) {t=(s=now)+fread(now,1,1<<16,stdin); if(s==t) return EOF;}
    return *s++;
}
inline int read()
{
    int x=0; char ch=gc();
    while(ch<'0'||'9'<ch) ch=gc();
    while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x;
}
int max(int x,int y) {return x>y?x:y;}
int const N=2e4+10;
int n,m,s[N];
int sa[N],rnk[N<<1],h[N];
int cnt[N*50],tmp[N],rnk1[N<<1];
void getSA()
{
    for(int i=1;i<=n;i++) cnt[s[i]]=1;
    for(int i=1;i<=1e6;i++) cnt[i]+=cnt[i-1];
    for(int i=1;i<=n;i++) rnk[i]=cnt[s[i]];
    for(int L=1;L<=n;L<<=1)
    {
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[i+L]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) tmp[cnt[rnk[i+L]]--]=i;
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) cnt[rnk[tmp[i]]]++;
        for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
        for(int i=n;i>=1;i--) sa[cnt[rnk[tmp[i]]]--]=tmp[i];
        int k=0; memcpy(rnk1,rnk,sizeof rnk);
        for(int i=1;i<=n;i++)
        {
            if(rnk1[sa[i]]!=rnk1[sa[i-1]]||rnk1[sa[i]+L]!=rnk1[sa[i-1]+L]) k++;
            rnk[sa[i]]=k;
        }
        if(k>=n) break;
    }
    for(int i=1,k=0;i<=n;i++)
    {
        if(rnk[i]==1) {h[1]=0; continue;}
        if(k) k--;
        while(s[i+k]==s[sa[rnk[i]-1]+k]) k++;
        h[rnk[i]]=k;
    }
}
int check(int x)
{
    int res=0;
    for(int i=1,fr=1;i<=n+1;i++)
        if(h[i]<x) res=max(res,i-fr+1),fr=i+1;
    return res;
}
int main()
{
    n=read(),m=read(); for(int i=1;i<=n;i++) s[i]=read();
    getSA();
    int L=1,R=n;
    while(L<=R)
    {
        int mid=L+R>>1;
        if(check(mid)>=m) L=mid+1;
        else R=mid-1;
    }
    printf("%d\n",R);
    return 0;
}

P.S.

注意在check(x)中我循环到了\(n+1\),否则统计不到处于末尾的\(h\geq len\)的区间。
同权限题BZOJ1717

posted @ 2018-03-22 21:11  VisJiao  阅读(129)  评论(0编辑  收藏  举报