题意: 给定区间 [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;
}