BZOJ 4310 二分+SA+RMQ

思路:

首先求出后缀数组和height数组,这样能得到本质不同的子串数目

这里利用:本质不同的子串=(LenSA[i]height[i])=∑(Len−SA[i]−height[i])利用SA[],height[]的定义很好想

然后要求最大值最小,显然二分,二分一个mid,求出第mid大的子串

然后贪心的检验,从后往前扫,当字典序超过二分的值时,划分一下,看划分个数与K的关系即可

中间涉及比较,用LCP实现即可,显然ST表非常方便

From Dad3zZ

//By SiriusRen
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=100050;
typedef long long ll;
int n,k,cntA[N],cntB[N],A[N],B[N],sa[N],tsa[N],rk[N],ht[N],Log[N],st[N][20];
int L,R;ll l,r,ans;
char s[N];
void SA(){
    for(int i=1;i<=n;i++)cntA[s[i]]++;
    for(int i=1;i<=256;i++)cntA[i]+=cntA[i-1];
    for(int i=n;i;i--)sa[cntA[s[i]]--]=i;
    rk[sa[1]]=1;
    for(int i=2;i<=n;i++)rk[sa[i]]=rk[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
    for(int l=1;rk[sa[n]]<n;l<<=1){
        memset(cntA,0,sizeof(cntA));
        memset(cntB,0,sizeof(cntB));
        for(int i=1;i<=n;i++)
            cntA[A[i]=rk[i]]++,
            cntB[B[i]=i+l<=n?rk[i+l]:0]++;
        for(int i=1;i<=n;i++)cntA[i]+=cntA[i-1],cntB[i]+=cntB[i-1];
        for(int i=n;i;i--)tsa[cntB[B[i]]--]=i;
        for(int i=n;i;i--)sa[cntA[A[tsa[i]]]--]=tsa[i];
        rk[sa[1]]=1;
        for(int i=2;i<=n;i++)rk[sa[i]]=rk[sa[i-1]]+(A[sa[i]]!=A[sa[i-1]]||B[sa[i]]!=B[sa[i-1]]);
    }
    for(int i=1,j=0;i<=n;i++){
        j=j?j-1:0,Log[i]=i!=1?Log[i>>1]+1:0;
        while(s[i+j]==s[sa[rk[i]-1]+j])j++;
        st[rk[i]][0]=ht[rk[i]]=j;
    }
    for(int j=1;j<=19;j++)for(int i=1;i+(1<<(j-1))<=n;i++)st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int lcp(int x,int y){
    if(x==y)return n-x+1;
    x=rk[x],y=rk[y];
    if(x>y)swap(x,y);x++;
    int t=Log[y-x+1];
    return min(st[x][t],st[y-(1<<t)+1][t]);
}
void get(ll k){
    for(int i=1;i<=n;i++){
        if(n-sa[i]-ht[i]+1<k)k=k-(n-sa[i]-ht[i]+1);
        else{L=sa[i],R=sa[i]+ht[i]+k-1;break;}
    }
}
bool cmp(int l1,int r1,int l2,int r2){
    int len1=r1-l1+1,len2=r2-l2+1,LCP=lcp(l1,l2);
    if(len1<=len2&&LCP>=len1)return 1;
    if(len1>len2&&LCP>=len2)return 0;
    if(LCP>=len1&&LCP>=len2)return len1<len2;
    return s[l1+LCP]<s[l2+LCP];
}
bool check(){
    int cnt=1,last=n;
    for(int i=n;i;i--){
        if(!cmp(i,last,L,R))cnt++,last=i;
        if(cnt>k)return 0;
    }return 1;
}
int main(){
    scanf("%d%s",&k,s+1),n=strlen(s+1),SA();
    for(int i=1;i<=n;i++)r+=n-sa[i]-ht[i]+1;
    while(l<=r){
        ll mid=(l+r)>>1;
        get(mid);
        if(check())r=mid-1,ans=mid;
        else l=mid+1;
    }get(ans);
    s[R+1]=0;printf("%s\n",s+L);
}

 

posted @ 2017-04-09 17:22  SiriusRen  阅读(267)  评论(0编辑  收藏  举报