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≤l≤r≤N) and k(1≤k≤N), 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;
}