HDU-6704 K-th occurrence(后缀数组+RMQ+主席树)

2 篇文章 0 订阅
1 篇文章 0 订阅

http://acm.hdu.edu.cn/showproblem.php?pid=6704

Time Limit: 3000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 829    Accepted Submission(s): 259


 

Problem Description

You are given a string S consisting of only lowercase english letters and some queries.

For each query (l,r,k), please output the starting position of the k-th occurence of the substring SlSl+1...Sr in S.

 

 

Input

The first line contains an integer T(1≤T≤20), denoting the number of test cases.

The first line of each test case contains two integer N(1≤N≤10^5),Q(1≤Q≤10^5), denoting the length of S and the number of queries.

The second line of each test case contains a string S(|S|=N) consisting of only lowercase english letters.

Then Q lines follow, each line contains three integer l,r(1≤lrN) and k(1≤kN), denoting a query.

There are at most 5 testcases which N is greater than 10^3.

 

 

Output

For each query, output the starting position of the k-th occurence of the given substring.

If such position don't exists, output −1 instead.

 

 

Sample Input

 

2

12 6

aaabaabaaaab

3 3 4

2 3 2

7 8 3

3 4 2

1 4 2

8 12 1

1 1

a

1 1 1

 

 

Sample Output

 

5

2

-1

6

9

8

1

 题意:[l,r]这段子串在原串中第k次出现的位置

思路:先摸一发鲲神

一开始以为是签到题,写了两发kmp,TLE了

因为子串很多,所以考虑后缀数组或者后缀自动机

在后缀数组中,

sa[i]表示字典序第i小的后缀的初始位置下标,sa[1~n]为有效值,sa[0]必定为n是无效值

rank[i]表示初始下标为i的后缀的排名,rank[0~n-1]为有效值,rank[n]必定为0无效值

height[i]表示suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀,height[2~n]为有效值

每个子串一定是某个后缀的前缀,所以排名为rank[l]的后缀一定包含[l,r]这个子串,但是包含[l,r]的不一定只有rank[l],在包含rank[l]的一段连续区间内的后缀都包含[l,r],那么如何确定区间的两端呢?

排名相邻的两个后缀的最长公共前缀是height[i],height数组在一定范围(包含[l,r]这个子串)具有单调性,比如a,ab,abc

只要一段与rank[l]连续的区间的height的最小值大于等于len那都包含[l,r]这个子串(RMQ求区间最小值)

所以问题就变成了求这个排名区间中第k大的下标(主席树求区间第k大) ,排名的下标不就是sa数组吗

 

嘤嘤嘤,人均会后缀数组+rmq+二分+主席树,再摸鲲神

 

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
const int MAXN=1e5+10;
int sa[MAXN];     
int t1[MAXN],t2[MAXN],c[MAXN];
int Rank[MAXN],height[MAXN];
void build_sa(int s[],int n,int m)
{
    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]=s[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]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;
        if(p>=n)break;
        m=p;
    }
}
void getHeight(int s[],int n)
{
    int i,j,k=0;
    for(i=0;i<=n;i++)Rank[sa[i]]=i;
    for(i=0;i<n;i++)
    {
        if(k)k--;
        j=sa[Rank[i]-1];
        while(s[i+k]==s[j+k])k++;
        height[Rank[i]]=k;
    }
}
char str[MAXN];
int s[MAXN];
int dp[N][22];
void rmq_st_min(int n){   
	for(int i=1;i<=n;i++) dp[i][0]=height[i];
	int m=log2(1.0*n);
	for(int j=1;j<=m;j++){
		int t=n-(1<<j)+1;
		for(int i=1;i<=t;i++){
			dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
		}
	}
}
int find_min(int l,int r){
	int k=log2(1.0*(r-l+1));
	return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
struct node{
	int lp,rp;
	int num;
}tr[N*22];
int root[N],tot=0;
int update(int pre,int l,int r,int pos){
	int now=++tot;
	tr[now]=tr[pre];
	tr[now].num++;
	if(l==r) return now;
	int mid=(l+r)>>1;
	if(pos<=mid) tr[now].lp=update(tr[pre].lp,l,mid,pos);
	else tr[now].rp=update(tr[pre].rp,mid+1,r,pos);
	return now;
}
int query(int pl,int pr,int l,int r,int k){
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(tr[tr[pr].lp].num-tr[tr[pl].lp].num>=k) return query(tr[pl].lp,tr[pr].lp,l,mid,k);
	else return query(tr[pl].rp,tr[pr].rp,mid+1,r,k-(tr[tr[pr].lp].num-tr[tr[pl].lp].num));
}
int main()
{   
    int T;
    scanf("%d",&T);
    while(T--)
    {   
        int n,m;
        scanf("%d%d",&n,&m);
        scanf("%s",str);
        for(int i=0;i<=n;i++)s[i]=str[i];
        build_sa(s,n+1,128);
        getHeight(s,n);
        rmq_st_min(n); 
		tot=0;
        for(int i=1;i<=n;i++){
        	root[i]=update(root[i-1],1,n,sa[i]+1);
		}
		while(m--){
			int l,r,k,mid;
			scanf("%d%d%d",&l,&r,&k);
			int len=r-l+1;
			int f=Rank[l-1];//由f两边扩,找到所有以[l,r]为前缀的后缀的排名
			int L=f,R=f;
			l=1,r=f;
			while(l<=r){
				mid=(l+r)>>1;
				int cnt=N;
				if(mid+1<=f) cnt=find_min(mid+1,f);
				if(cnt>=len){
					r=mid-1;
					L=mid;
				}
				else{
					l=mid+1;
				}
			}
			l=f,r=n;
			while(l<=r){
				mid=(l+r)>>1;
				int cnt=N;
				if(f+1<=mid) cnt=find_min(f+1,mid);
				if(cnt>=len){
					l=mid+1;
					R=mid;
				}
				else{
					r=mid-1;
				}
			}
			if(R-L+1<k) printf("-1\n");
			else printf("%d\n",query(root[L-1],root[R],1,n,k));
		}
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值