K-th occurrence

题意: 给定区间 [l,r] 和 k,求和区间 [l,r] 相同的串的第k个位置在哪?

题解:利用后缀数组求出height数组,因为排名相近的两个串是最想像的串,所以st表求区间最小值,找到字符相同的串的区间,然后利用主席树存sa值求第k大。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=200005;
/*****************   后缀数组    ****************/
int t1[maxn],t2[maxn],c[maxn],num[maxn];
int rak[maxn],height[maxn],sa[maxn];
/*
sa[i] : 排名为i的后缀位置
rak[i] : 后缀位置为i的排名
height[i] : 排名i和排名i-1的最长后缀

*/
int m; // ascii大小
bool cmp(int *r,int a,int b,int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(char str[],int n)
{
    n++;
    int i, j, p, *x = t1, *y = t2;
    for(i = 0;i < m;i++)c[i] = 0;
    for(i = 0;i < n;i++)c[x[i] = str[i]]++;
    for(i = 1;i < m;i++)c[i] += c[i-1];
    for(i = n-1;i >= 0;i--)sa[--c[x[i]]] = i;
    for(j = 1;j <= n; j <<= 1)
    {
        p = 0;
        for(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 < m; i++)c[i] = 0;
        for(i = 0; i < n; i++)c[x[y[i]]]++;
        for(i = 1; i < m;i++)c[i] += c[i-1];
        for(i = n-1; i >= 0;i--)sa[--c[x[y[i]]]] = y[i];
        swap(x,y);
        p = 1; x[sa[0]] = 0;
        for(i = 1;i < n;i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p >= n)break;
        m = p;
    }
    int k = 0;
    n--;
    for(i = 0;i <= n;i++)rak[sa[i]] = i;
    for(i = 0;i < n;i++)
    {
        if(k)k--;
        j = sa[rak[i]-1];
        while(str[i+k] == str[j+k])k++;
        height[rak[i]] = k;
    }
}

/*****************   主席树区间第k大    ****************/
int cnt,root[maxn];
struct node{
    int l,r,sum;
}tree[maxn*40];

void init(){
    cnt=tree[0].l=tree[0].r=tree[0].sum=0;
}

void add(int l,int r,int &x,int y,int pos){
    tree[++cnt]=tree[y]; tree[cnt].sum++; x=cnt;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(pos<=mid) add(l,mid,tree[x].l,tree[y].l,pos);
    else add(mid+1,r,tree[x].r,tree[y].r,pos);
}

int query(int l,int r,int x,int y,int k){

    if(l==r) return l;
    int sum=tree[tree[y].l].sum-tree[tree[x].l].sum;
    int mid=(l+r)>>1;
    if(sum<k) return query(mid+1,r,tree[x].r,tree[y].r,k-sum);
    else return query(l,mid,tree[x].l,tree[y].l,k);
}
/*****************   st表区间最小值    ****************/
int minPoint[maxn][20];
void queryInit(int n)
{
    for (int i = 1; i <= n; i++)
    {
        minPoint[i][0] = height[i];
    }
    for (int j = 1; (1 << j) <= n; j++)
    {
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
        {
            int p = (1 << (j - 1));
            minPoint[i][j] = min(minPoint[i][j - 1], minPoint[i + p][j - 1]);
        }
    }
}
int queryMin(int l, int r)
{
    int k = log2((double)(r - l + 1));
    return min(minPoint[l][k], minPoint[r - (1 << k) + 1][k]);
}

/*****************   二分部分    ****************/
int checkl(int l,int r,int x,int len){

    int tag=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(queryMin(mid,x)>=len) r=mid-1,tag=mid;
        else l=mid+1;
    }
    return tag;
}
int checkr(int l,int r,int x,int len){

    int tag=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(queryMin(x,mid)>=len) l=mid+1,tag=mid;
        else r=mid-1;
    }
    return tag;
}

char str[maxn];
int main()
{
    int T,n,q;
    scanf("%d",&T);
    while(T--){
        scanf("%d %d",&n,&q);
        scanf("%s",str); m=300;
        da(str,n); root[0]=0; init();
        for(int i=1;i<=n;i++) add(0,n,root[i],root[i-1],sa[i]);
        queryInit(n);
        int l,r,k;
        while(q--){
            scanf("%d %d %d",&l,&r,&k);
            int len=r-l+1;
            int ra=rak[l-1];
            int a=checkl(2,ra,ra,len);
            int b=checkr(ra+1,n,ra+1,len);
            int rs,rt;
            if(a) rs=a; else rs=ra+1;
            if(b) rt=b; else rt=ra;
            int x=rt-rs+2;
            if(x<k) printf("-1\n");
            else {
                printf("%d\n",query(0,n,root[max(rs-2,0)],root[rt],k)+1);
            }
        }
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值