bzoj3998[TJOI2015]弦论

题意:给一个串,求:

1.本质不同的所有子串中字典序第k大的(即相同子串多次出现算一个)

2.所有子串中字典序第k大的(即相同子串多次出现算多个)

对于第一种情况,后缀数组的做法很经典:按照所有后缀的字典序依次加入后缀,新加入一个后缀会产生”这个后缀的长度减去这个后缀的height值”这么多个子串,显然每次新产生的子串比之前产生的所有子串的字典序都大,那么就可以在前缀和上二分了,预处理O(nlogn),单组询问O(logn)(不考虑询问的输出)

对于第二种情况,可以后缀自动机,然而我不会,于是我们依然考虑后缀数组.和第一种情况类似,我们需要考虑每个后缀产生多少个子串.显然每个后缀都会产生这个后缀的长度这么多个子串,但这并没有什么用.我们不妨考虑把一个子串的多次出现全都算到这个子串在所有出现的后缀中字典序最小的那个后缀上,那么只需按照height从大到小合并,并查集维护一下就可以了.然后就可以前缀和+二分找出第k大的子串在是哪一个后缀所产生的字典序第几大的子串.这之后我们还需要支持查询某个后缀产生的子串中第k大的串是多少,我不太会处理,于是强行又写了一遍按height合并所有后缀,预处理O(nlogn)+单组询问O(nlogn)(并查集只写了路径压缩所以是log的),非常虚,没T真是感动.

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=500005;
int sa[maxn],rank[maxn],height[maxn];
int tmp[2][maxn],key[maxn],sum[maxn];
char str[maxn];
void getsa(int n,int m){
  int i,j,k,p,*rk=tmp[0],*res=tmp[1];
  for(i=0;i<m;++i)sum[i]=0;
  for(i=0;i<n;++i)sum[rk[i]=str[i]]++;
  for(i=1;i<m;++i)sum[i]+=sum[i-1];
  for(i=n-1;i>=0;--i)sa[--sum[rk[i]]]=i;
  for(j=1,p=0;p<n;j<<=1,m=p){
    for(i=0;i<m;++i)sum[i]=0;
    for(p=0,i=n-j;i<n;++i)res[p++]=i;
    for(i=0;i<n;++i)if(sa[i]>=j)res[p++]=sa[i]-j;
    for(i=0;i<n;++i)sum[key[i]=rk[res[i]]]++;
    for(i=1;i<m;++i)sum[i]+=sum[i-1];
    for(i=n-1;i>=0;--i)sa[--sum[key[i]]]=res[i];
    for(res[sa[0]]=0,p=1,i=1;i<n;++i)
      res[sa[i]]=(rk[sa[i]]==rk[sa[i-1]]&&rk[sa[i]+j]==rk[sa[i-1]+j])?p-1:p++;
    swap(res,rk);
  }
  for(i=1;i<n;++i)rank[sa[i]]=i;
  for(i=0,k=0;i<n-1;height[rank[i++]]=k)
    for(k?k--:0,j=sa[rank[i]-1];str[j+k]==str[i+k];++k);
}
long long cnt[maxn];
int seq[maxn];
int ufs[maxn],sz[maxn];
int find(int x){return x==ufs[x]?x:ufs[x]=find(ufs[x]);}
void link(int a,int b){
  int ra=find(a),rb=find(b);
  sz[rb]+=sz[ra];ufs[ra]=rb;
}
bool cmp(const int &a,const int &b){return height[a]<height[b];}
int mark[maxn];
int main(){
  scanf("%s",str);
  int n=strlen(str);
  getsa(n+1,256);
  int t,k;
  scanf("%d%d",&t,&k);
  if(t==0){
    for(int i=1;i<=n;++i){
      cnt[i]=n-sa[i]-height[i];
    }
    for(int i=2;i<=n;++i)cnt[i]+=cnt[i-1];
    if(cnt[n]<k){
      printf("-1\n");return 0;
    }
    int pos=lower_bound(cnt,cnt+n+1,(long long)k)-cnt;
    int num=k-cnt[pos-1];
    int l=sa[pos],r=sa[pos]+height[pos]+num-1;
    for(int i=l;i<=r;++i)printf("%c",str[i]);printf("\n");
  }else{
    for(int i=1;i<=n;++i)cnt[i]=n-sa[i];
    for(int i=0;i<=n;++i){
      ufs[i]=i;sz[i]=1;
    }
    for(int i=1;i<=n;++i)seq[i]=i;
    sort(seq+1,seq+n+1,cmp);
    for(int i=n;i>=1;--i){
      int rt0=find(seq[i]-1),rt1=find(seq[i]);
      cnt[rt0]+=height[seq[i]]*1ll*sz[rt1];cnt[rt1]-=height[seq[i]]*1ll*sz[rt1];
      link(seq[i],seq[i]-1);
    }
    for(int i=1;i<=n;++i)cnt[i]+=cnt[i-1];
    if(cnt[n]<k){
      printf("-1\n");return 0;
    }
    int pos=lower_bound(cnt,cnt+n+1,(long long)k)-cnt;
    int num=cnt[pos]-k+1;
    for(int i=0;i<=n;++i)ufs[i]=i,sz[i]=1;
    for(int i=n;i>=1;--i){//printf("height[%d]==%d\n",seq[i],height[seq[i]]);
      link(seq[i],seq[i]-1);mark[height[seq[i]]]=sz[find(pos)];
    }
    for(int i=n-sa[pos];i>=1;--i)if(!mark[i])mark[i]=max(1,mark[i+1]);
    //    for(int i=n-sa[pos];i>=1;--i)printf("%d\n",mark[i]);
    int l=sa[pos],r;
    for(int i=n-sa[pos];i>=1;--i){
      num-=mark[i];
      if(num<=0){r=sa[pos]+i-1;break;}
    }
    for(int i=l;i<=r;++i)printf("%c",str[i]);printf("\n");
  }
  return 0;
}

 

posted @ 2017-03-08 15:11  liu_runda  阅读(768)  评论(0编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难