zoukankan      html  css  js  c++  java
  • 【后缀数组】【题解】2019CCPC网络选拔赛 K-th occurrence(后缀树组+划分树+ST表+RMQ+二分)

    2019CCPC网络选拔赛1003 HDU6704


    题目大意:

    T个测试样例。一个长度为N的字符串S,之后Q个[l,r,k],表示一个子串S[l,r],求出第k个该子串的下标。起始坐标为1。不存在输出-1。

    数据范围:1≤T≤20,  1≤N≤105,  1≤Q≤105,  1≤l≤r≤N,  1≤k≤N,  |S|=N; 

    赛后补题。参考题解说后缀树组+划分树+ST表+二分。

    比赛的时候只会后缀树组不会划分树,赛后仔细想,觉得后缀数组可以,然而并不,会TLE。

    补提的时候先是采用后缀树组+划分树+RMQ+二分,还是TLE了。

    之后改成后缀树组+划分树+ST表+二分,RMQ是随手用。

    //kkkek
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    const int mod=998244353;
    const int maxn=1e5+50;
    
    /*主代码:后缀数组+ST表+划分树+二分check*/
    
    /***************后缀数组**************/  
    int wa[maxn],wb[maxn],wv[maxn];
    int cmp(int *r,int a,int b,int k)
    {
        return r[a]==r[b]&&r[a+k]==r[b+k];
    }
    void da(int *r,int *sa,int n,int m,int *ws)
    {
        int i,j,p,*x=wa,*y=wb,*t;
        for(i=0;i<m;i++)ws[i]=0;
        for(i=0;i<n;i++)ws[x[i]=r[i]]++;
        for(i=1;i<m;i++)ws[i]+=ws[i-1];
        for(i=n-1;i>=0;i--)sa[--ws[x[i]]]=i;
        for(j=1,p=1;p<n;j*=2,m=p)
        {
            for(p=0,i=n-j;i<n;i++)y[p++]=i;
            for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
            for(i=0;i<n;i++)wv[i]=x[y[i]];
            for(i=0;i<m;i++)ws[i]=0;
            for(i=0;i<n;i++)ws[wv[i]]++;
            for(i=1;i<m;i++)ws[i]+=ws[i-1];
            for(i=n-1;i>=0;i--)sa[--ws[wv[i]]]=y[i];
            for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        }
        return;
    }
    int height[maxn];
    void calheight(int *r,int *sa,int n,int *rank)
    {
        memset(height,0,sizeof(height));
        int i,j,k=0;
        for(i=1;i<=n;i++)rank[sa[i]]=i;
        for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
        return;
    }//sa[排名]=下标 rank[下标]=排名
    //height[排名i]=排名为i的数组与排名为i-1数组的最长前缀的长度 
    
    /******************划分树******************/ 
    int sorted[maxn];
    int num[20][maxn],val[20][maxn];
    void build(int l,int r,int ceng)
    {
        if(l==r)return;
        int mid=(l+r)>>1,isame=mid-l+1,i;
        for(i=l;i<=r;i++)if(val[ceng][i]<sorted[mid])isame--;
        int ln=l,rn=mid+1;
        for(i=l;i<=r;i++)
        {
            if(i==l)num[ceng][i]=0;
            else num[ceng][i]=num[ceng][i-1];
            if(val[ceng][i]<sorted[mid]||val[ceng][i]==sorted[mid]&&isame>0)
            {
                val[ceng+1][ln++]=val[ceng][i];
                num[ceng][i]++;
                if(val[ceng][i]==sorted[mid])isame--;
            }
            else val[ceng+1][rn++]=val[ceng][i];
        }
        build(l,mid,ceng+1);build(mid+1,r,ceng+1);
    }
    int look(int ceng,int sl,int sr,int l,int r,int k)
    {
        if(sl==sr)return val[ceng][sl];
        int ly;
        if(l==sl)ly=0;
        else ly=num[ceng][l-1];
        int tolef=num[ceng][r]-ly;
        if(tolef>=k)
        {
            return look(ceng+1,sl,(sl+sr)/2,sl+ly,sl+num[ceng][r]-1,k);
        }
        else
        {
            int lr=(sl+sr)/2+1+(l-sl-ly);
            return look(ceng+1,(sl+sr)/2+1,sr,lr,lr+r-l+1-tolef-1,k-tolef);
        }
    }//(0,1,n,l,r,k)找数组l与r之间第k大的数的数值,返回该数值
    
    /***********************ST表******************/
    int st[maxn][25];
    void initST(int n,int *a)
    {
        memset(st,0,sizeof(st));
        for(int i=0;i<=n;i++)st[i][0]=a[i];
        for(int j=1;(1<<j)<=n;j++)
            for(int i=0;i+(1<<j)-1<=n;i++)
                st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
    }
    int askST(int l,int r)
    {
        if(l>r)swap(l,r);
        int k=log2(r-l+1);//(int)(log((double)(r-l+1))/log(2.0));
        return min(st[l][k],st[r-(1<<k)+1][k]);
    }//askST(l,r)找数组中l,r之间的最小值,返回该最小值 
    int askRMQ(int ra,int rb)
    {
        if(ra>rb)swap(ra,rb);
        int k=0;
        while(1<<(k+1)<=rb-ra)k++;
        return min(st[ra+1][k],st[rb-(1<<k)+1][k]);//len
    }//askRMQ(rank[a],rank[b])找下标a,b(或者排名ra,rb)的后缀数组的最长公共前缀
    //前驱套用st表前驱(长得一毛一样当然就不再init一遍啦)
    
    /******************二分查找*********************************/ 
    //用ST表判断长度是否合理  不合理二分缩小查找范围 
    int check(int l,int r,int ju,int len)
    {
        if(l==r)return l;
        int mid=(l+r)>>1;
        if(!ju)
        {
            if(l>r)return l;
            if(askST(l,r)>=len)return r;
            if(askST(l,mid)>=len)return check(mid,r-1,0,len);
            else
            {
                if(l+1==mid)return l;
                return check(l,mid-1,0,len);
            }
        }
        else
        {
            if(r<l)return r;
            if(askST(l,r)>=len)return l;
            if(askST(mid,r)>=len)return check(l+1,mid,1,len);
            else
            {
                if(mid+1==r)return r;
                return check(mid+1,r,1,len);
            }
        }
    }//找到给定子串l的rank[l]排位前后的子串的最小下标与最大小标,即,上下界
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            int n,q,i,j,r[maxn]={0},sa[maxn]={0},ws[maxn]={0},rank[maxn]={0},L,R,k,len,ra;
            char s[maxn]="";
            scanf("%d%d",&n,&q);
            scanf("%s",s);
            for(i=0;i<n;i++)r[i]=s[i]-'a'+1;
            r[n]=0;n++;
            da(r,sa,n,30,ws);//后缀数组求出sa[]和rank[]
            calheight(r,sa,n-1,rank);//得height[]数组 为之后查找上下界做准备 
            
            for(i=0;i<n;i++)sorted[i]=sa[i],val[0][i]=sa[i];
            sort(sorted+1,sorted+n);//为划分树准备
            build(1,n-1,0);//划分树预处理 
            
            initST(n-1,height);//ST表预处理 
            
            while(q--)
            {
                scanf("%d%d%d",&L,&R,&k);
                L--;R--;//下标与题意下标对应
                 
                ra=rank[L];
                len=R-L+1;
                
                //二分查找 
                if(height[ra]<len&&height[ra+1]!=0&&askRMQ(ra,ra+1)>=len)
                {
                    L=ra;
                    R=check(ra+1,n-1,0,len);//这里!一定要ra+1 因为height[].... 为了这个debug好久=皿= 
                }
                else if(height[ra]<len)
                {
                    L=R=ra;
                }
                else
                {
                    if(height[ra+1]==0)R=ra;
                    else R=check(ra,n-1,0,len);
                    L=check(1,ra,1,len);
                }
                if(askRMQ(ra,L-1)>=len&&L!=1)L--;
                if(R-L+1<k)printf("-1
    ");
                else printf("%d
    ",look(0,1,n-1,L,R,k)+1);
            }
        }
    }

    一点一点打出来,加上debug,好艰难 (: 3_~)_

    但是...... 最后ac了就好爽啊o(* ̄︶ ̄*)o❀(๑╯◡╰๑)❀

    2019-08-28

  • 相关阅读:
    38861cba61c66739c1452c3a71e39852.ttf net::ERR_ABORTED 404 (Not Found)
    php 进制转换base_convert
    mysql find_in_set 函数 使用方法
    xmind 破解
    python 获取 一个正整数的二进制
    算法 求一个数的平方根
    详细的描述一个测试活动完整的过程。
    常见的测试用例设计方法都有哪些?请分别以具体的例子来说明这些方法在测试用例设计工作中的应用。
    HTTP 协议中 Vary 的一些研究
    svn cleanup failed–previous operation has not finished; run cleanup if it was interrupted
  • 原文地址:https://www.cnblogs.com/kkkek/p/11427299.html
Copyright © 2011-2022 走看看