hdu6704 K-th occurrence(后缀数组height[]上建st表+sa[]上建可持久化线段树)

题意:

给定长度为n的串,q次询问。
每次询问给出L,R,k,
问子串[L,R]在串中第k次出现的起点,如果没有第k次则输出-1

数据范围:n,q<=1e5

解法:
先跑后缀数组
子串[L,R]是suf[L]的前缀,排名为rk[L]
在height[]上建立st表,
在height[]上二分出height[rk[L]]左右两边,包含子串[L,R]的sa[]的范围[st,ed]
接下来就是求sa[st,ed]中的第k大了,用可持久化线段树搞
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e5+5;
struct SA{
    static const int N=1e5+5;
    char s[N];
    int sa[N],rk[N],oldrk[N<<1],id[N],px[N],cnt[N];
    int ht[N];
    int n;//字符串长度
    int m=300;//初始字符集大小
    void init(){//init
        memset(cnt,0,sizeof cnt);
        m=300;
    }
    bool cmp(int x,int y,int w){
        return oldrk[x]==oldrk[y]&&oldrk[x+w]==oldrk[y+w];
    }
    void getSA(){
        int i,p,w;
        for(i=1;i<=n;i++)cnt[rk[i]=s[i]]++;
        for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
        for(i=n;i>=1;i--)sa[cnt[rk[i]]--]=i;
        //
        for(w=1;w<n;w<<=1,m=p){
            for(p=0,i=n;i>n-w;i--)id[++p]=i;
            for(i=1;i<=n;i++)if(sa[i]>w)id[++p]=sa[i]-w;
            memset(cnt,0,sizeof cnt);
            for(i=1;i<=n;i++)cnt[px[i]=rk[id[i]]]++;
            for(i=1;i<=m;i++)cnt[i]+=cnt[i-1];
            for(i=n;i>=1;i--)sa[cnt[px[i]]--]=id[i];
            memcpy(oldrk,rk,sizeof rk);
            for(p=0,i=1;i<=n;i++){
                rk[sa[i]]=cmp(sa[i],sa[i-1],w)?p:++p;
            }
        }
    }
    void getHT(){
        for(int i=1,k=0;i<=n;i++){
            if(k)k--;
            while(s[i+k]==s[sa[rk[i]-1]+k])k++;
            ht[rk[i]]=k;
        }
    }
    //
    static const int maxd=(int)(log(N)/log(2))+1;
    int mi[N][maxd+5];
    void getRMQ(){//在ht[]上建立st表
        for(int i=1;i<=n;i++){
            mi[i][0]=ht[i];
        }
        for(int j=1;j<=maxd;j++){
            for(int i=1;i+(1<<j)-1<=n;i++){
                mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
            }
        }
    }
    int askmi(int l,int r){
        int k=log2(r-l+1);
        return min(mi[l][k],mi[r-(1<<k)+1][k]);
    }
    //
}sa;
int lc[maxm*40],rc[maxm*40],cnt[maxm*40];
int rt[maxm],tot;
void init(){
    tot=0;
    rt[0]=0;
    lc[0]=rc[0]=0;
    cnt[0]=0;
}
void update(int x,int l,int r,int last,int &k){
    k=++tot;
    lc[k]=lc[last];
    rc[k]=rc[last];
    cnt[k]=cnt[last];
    cnt[k]++;
    if(l==r)return ;
    int mid=(l+r)/2;
    if(x<=mid)update(x,l,mid,lc[last],lc[k]);
    else update(x,mid+1,r,rc[last],rc[k]);
}
int ask(int k,int l,int r,int L,int R){
    if(l==r)return l;
    int mid=(l+r)/2;
    int lcc=cnt[lc[R]]-cnt[lc[L]];
    if(lcc>=k){
        return ask(k,l,mid,lc[L],lc[R]);
    }else{
        k-=lcc;
        return ask(k,mid+1,r,rc[L],rc[R]);
    }
}
signed main(){
    int T;cin>>T;
    while(T--){
        //init
        init();
        sa.init();
        char *s=sa.s;
        int &n=sa.n;
        int *ss=sa.sa;
        int *rk=sa.rk;
        //input
        int q;
        scanf("%d%d",&n,&q);
        scanf("%s",s+1);
        //SA
        sa.getSA();
        sa.getHT();
        sa.getRMQ();
        //在sa[]上建可持久化线段树
        for(int i=1;i<=n;i++){
            update(ss[i],1,n,rt[i-1],rt[i]);
        }
        //
        while(q--){
            int ql,qr,k;scanf("%d%d%d",&ql,&qr,&k);
            int len=qr-ql+1;
            int L=rk[ql],R=rk[ql];
            int l=1,r=rk[ql]-1;
            while(l<=r){
                int mid=(l+r)/2;
                if(sa.askmi(mid+1,rk[ql])>=len){//lcp是[mid+1,rk[ql]]之间height的min
                    L=mid,r=mid-1;
                }else{
                    l=mid+1;
                }
            }
            l=rk[ql]+1,r=n;
            while(l<=r){
                int mid=(l+r)/2;
                if(sa.askmi(rk[ql]+1,mid)>=len){
                    R=mid,l=mid+1;
                }else{
                    r=mid-1;
                }
            }
            if(R-L+1<k){
                puts("-1");continue;
            }
            int ans=ask(k,1,n,rt[L-1],rt[R]);
            printf("%d\n",ans);
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值