• 【TJOI2015】弦论 (后缀数组)


    前言:

    多好的题啊!

    我理论$O(nlog_2n)$的后缀数组还带个常数26,竟然跑的比$O(n)$的后缀自动机还快,全场 Rak 1?

    Description

    为了提高智商,ZJY开始学习弦论。这一天,她在《 String theory》中看到了这样一道问题:对于一个给定的长度为n的字符串,求出它的第k小子串是什么。你能帮帮她吗?

    Input

    第一行是一个仅由小写英文字母构成的字符串s

    第二行为两个整数t和k,t为0则表示不同位置的相同子串算作一个,t为1则表示不同位置的相同子串算作多个。k的意义见题目描述。

    Output

    输出数据仅有一行,该行有一个字符串,为第k小的子串。若子串数目不足k个,则输出-1。

    题解:

    相信T=0,大家都会做,这是后缀数组的一个经典问题。

    具体就是,每个子串都是一个后缀的前缀。

    从头到尾扫,每个后缀都会有 $n-sa[i]+1-height[i]$ 个本质不同的子串,即可。

    重点是T=1时,怎么用后缀数组解决?

    由于我比较菜,我想了一个大暴力,一位一位的枚举!

    因为我们前面的字母是确定的,那么当前面的字母一样的时候,后面一个字母一定是单调不下降的。

    那我们就能二分求出这个字母最后一个的位置。

    我们建一个后缀长度的前缀和,那我们就能求出以这个字母开头的子串有多少个。

    当个数大于$k$时,就确定了这个字母,否则$k$减去,继续枚举下一个字母。

    枚举完一位继续下一位,那我们就能把范围缩小,知道求出答案。

    具体看代码……

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 using namespace std;
      5 
      6 int n,t,k;
      7 long long sum[500005];
      8 char s[500005];
      9 struct SA{
     10     char s[500005];
     11     int tp[500005],rak[500005];
     12     int tax[500005],sa[500005];
     13     int n,m,height[500005];
     14     void build(char str[]){
     15         memcpy(s,str,sizeof(s));
     16         n=strlen(s+1);
     17         build_sa(rak,tp);
     18         build_height();
     19     }
     20     void sort(int a[],int b[]){
     21         for(int i=1;i<=m;i++)tax[i]=0;
     22         for(int i=1;i<=n;i++)tax[a[i]]++;
     23         for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
     24         for(int i=n;i>=1;i--)sa[tax[a[b[i]]]--]=b[i];
     25     }
     26     bool comp(int r[],int a,int b,int k){
     27         return r[a]==r[b]&&r[a+k]==r[b+k];
     28     }
     29     void build_sa(int a[],int b[]){
     30         for(int i=1;i<=n;i++)
     31         m=max(m,a[i]=s[i]-'a'+1),b[i]=i;
     32         sort(a,b);
     33         for(int p=0,j=1;p<n;j<<=1,m=p){
     34             p=0;
     35             for(int i=1;i<=j;i++)b[++p]=n-j+i;
     36             for(int i=1;i<=n;i++)if(sa[i]>j)b[++p]=sa[i]-j;
     37             sort(a,b);
     38             int *t=a;a=b;b=t;
     39             a[sa[1]]=p=1;
     40             for(int i=2;i<=n;i++)
     41             a[sa[i]]=comp(b,sa[i],sa[i-1],j)?p:++p;
     42         }
     43         for(int i=1;i<=n;i++)rak[sa[i]]=i;
     44     }
     45     void build_height(){
     46         for(int i=1,j=0;i<=n;i++){
     47             if(j)j--;
     48             while(s[i+j]==s[sa[rak[i]-1]+j])j++;
     49             height[rak[i]]=j;
     50         }
     51     }
     52 }a;
     53 
     54 int main(){
     55     scanf("%s%d%d",s+1,&t,&k);
     56     a.build(s);
     57     n=strlen(s+1);
     58     if(t==0){
     59         for(int i=1;i<=n;i++){
     60             int c=n-a.sa[i]+1-a.height[i];
     61             if(k<=c){
     62                 for(int j=a.sa[i];j<=a.sa[i]+a.height[i]+k-1;j++)
     63                     putchar(s[j]);
     64                 return 0;
     65             }else k-=c;
     66         }
     67         printf("-1");
     68     }else{
     69         for(int i=1;i<=n;i++)        //处理前缀和 
     70             sum[i]=sum[i-1]+n-a.sa[i]+1;
     71         if(sum[n]<k)return printf("-1"),0;    //子串不够输出-1 
     72         int L=1,R=n;
     73         for(int i=1;i<=n;i++){
     74             int tmp=L;
     75             for(int j='a';j<='z';j++){    //枚举 a~z 
     76                 int l=tmp,r=R;
     77                 while(l<=r){    //二分找这个字母的最后一个位置 
     78                     int mid=l+r>>1;
     79                     if(s[a.sa[mid]+i-1]>j)r=mid-1;
     80                     else l=mid+1;
     81                 }
     82                 long long t=sum[r]-sum[tmp-1]-1LL*(r-tmp+1)*(i-1);
     83                 //现在枚举的区间有多少个子串 
     84                 //减是因为减去前面枚举过得位置 
     85                 if(k<=r-tmp+1){
     86                     //现在要查的比现在字母的个数少,说明这个字母就是结束的位置 
     87                     for(int j=a.sa[tmp];j<=a.sa[tmp]+i-1;j++)
     88                         putchar(s[j]);
     89                     return 0;
     90                 }
     91                 if(t>=k){    //说明这位就是这个字母,减去字母个数 
     92                     L=tmp,R=r;
     93                     k-=r-tmp+1;
     94                     break;
     95                 }
     96                 tmp=r+1,k-=t;    //不是,继续枚举 
     97             }
     98             if(n-a.sa[L]+1==i)L++;    //如果下一位为空,就不用算了。 
     99         }
    100     }
    101 }
  • 相关阅读:
    【原创】大叔经验分享(53)kudu报错unable to find SASL plugin: PLAIN
    【原创】大叔经验分享(52)ClouderaManager修改配置报错
    【原创】大数据基础之Impala(3)部分调优
    【原创】大数据基础之Kudu(3)primary key
    【原创】大叔经验分享(51)docker报错Exited (137)
    【原创】大数据基础之Logstash(5)监控
    【原创】大数据基础之Logstash(4)高可用
    【原创】Linux基础之vi&vim基础篇
    【原创】大叔经验分享(50)hue访问mysql(librdbms)
    【原创】大叔经验分享(49)hue访问hdfs报错/hue访问oozie editor页面卡住
  • 原文地址:https://www.cnblogs.com/ezoiLZH/p/9471261.html
Copyright © 2020-2023  润新知