layout: post
title: HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并)
author: "luowentaoaa"
catalog: true
tags:
mathjax: true
- kuangbin
- 字符串
题意
给出一个长度为(n)字符串(s), (m)次询问,每次问你字符串(s)区间内的字符串(l,r)第(k)次出现的位置。
思路
比赛的时候一眼就知道是(sa)但是对于(sa)实在不喜欢,于是比赛的时候整场用(sam)硬怼。已经想到了用主席树记录(endpos)位置,然后从后往前推过去,但是比赛时还不会在(sam)上玩权值线段树合并,赛后学习了一下。
首先,这题我们可以在(sam)上的每个新开节点上存储这个节点是在原串的那个位置是把。
然后对于一个串(i)他的(father)肯定是他的(后缀) 所以他的(endpos)肯定也是他(father)的(endpos)
同时一个(father)不仅仅只有(i)这个儿子 所以(father)的继承是从很多儿子那边的来的,于是我们需要搞笑处理继承效果,这里用到了权值线段树合并,线段树下标是(endpos)在(s)的位置。
这样我们就可以通过找到一个点而得出这个点所有的(endpos) ,也可以很快的在权值线段树中找到第(k)大的$endpos (。但是)Q$次查询每次都可能用一个长度(1e5)的串进入(sam)中找这个点的位置,于是我们考虑离线操作,将询问按照(r)排序。但是我们找到了一个点之后可能这个长度太大了,于是我们需要跳到这个点的(father) 知道这个点的长度刚好大于等于我们需要的长度,这样答案才完整。因为(father)的长度是单调的,于是我们可以考虑用树上倍增 快速找到合适的(father)。
于是这题就愉快的结束了。真爽啊。debug都不需要。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn=5e5+50;
int ls[maxn*80],rs[maxn*80],rt[maxn*80],sum[maxn*80],cnt;
void update(int &o,int l,int r,int pos){
if(!o)o=++cnt;
if(l==r){
sum[o]++;return;
}
int mid=(l+r)/2;
if(pos<=mid)update(ls[o],l,mid,pos);
else update(rs[o],mid+1,r,pos);
sum[o]=sum[ls[o]]+sum[rs[o]];
}
int merger(int o,int pre,int l,int r){
if(!o)return pre;if(!pre)return o;
int k=++cnt;
if(l==r){
sum[k]=sum[o]+sum[pre];
return k;
}int mid=(l+r)/2;
ls[k]=merger(ls[o],ls[pre],l,mid);
rs[k]=merger(rs[o],rs[pre],mid+1,r);
sum[k]=sum[ls[k]]+sum[rs[k]];
return k;
}
int query(int o,int l,int r,int k){
if(sum[o]<k)return -1;
if(l==r)return l;
int mid=(l+r)/2;
if(sum[ls[o]]>=k)return query(ls[o],l,mid,k);
else return query(rs[o],mid+1,r,k-sum[ls[o]]);
}
struct Q{
int l,r,k,id;
bool operator<(const Q &o)const{
return r<o.r;
}
}my[maxn];
int n;
char s[maxn];
int ans[maxn];
struct SAM{
int fa[maxn],ch[maxn][26],maxlen[maxn],tot,last;
int sz[maxn],a[maxn],b[maxn];
void init(){
cnt=0;
fill(rt,rt+n*80,0);
fill(ls,ls+n*80,0);
fill(rs,rs+n*80,0);
fill(sum,sum+n*80,0);
tot=last=1;maxlen[1]=fa[1]=0;
memset(ch[1],0,sizeof(ch[1]));
}
void add(int x,int id){
int np=++tot,p=last;last=np;update(rt[np],1,n,id);
maxlen[np]=maxlen[p]+1;sz[np]=1;
memset(ch[np],0,sizeof(ch[np]));
while(p&&!ch[p][x])ch[p][x]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][x];
if(maxlen[q]==maxlen[p]+1)fa[np]=q;
else{
int nq=++tot;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q],fa[np]=fa[q]=nq;
maxlen[nq]=maxlen[p]+1;
while(p&&ch[p][x]==q)ch[p][x]=nq,p=fa[p];
}
}
}
int pa[maxn][25];
void Sort(){///拓扑排序 得到每个集合里的串出现次数
for(int i=1;i<=tot;i++)a[i]=0;
for(int i=1;i<=tot;i++)a[maxlen[i]]++;
for(int i=1;i<=tot;i++)a[i]+=a[i-1];
for(int i=1;i<=tot;i++)b[a[maxlen[i]]--]=i;
for(int i=tot;i;i--)sz[fa[b[i]]]+=sz[b[i]];
for(int i=tot;i;i--){
int k=b[i];
pa[k][0]=fa[k];
rt[fa[k]]=merger(rt[fa[k]],rt[k],1,n);
}
for(int i=1;i<=20;i++){
for(int j=1;j<=tot;j++){
int v=pa[j][i-1];
pa[j][i]=pa[v][i-1];
}
}
}
int go(int id,int len){
for(int i=20;i>=0;i--){
if(maxlen[pa[id][i]]>=len)id=pa[id][i];
}
return id;
}
int m;
void slove(){
for(int i=1;i<=m;i++){
cin>>my[i].l>>my[i].r>>my[i].k;
my[i].id=i;
}
sort(my+1,my+1+m);
int now=1;
int tail=1;
for(int i=1;i<=n;i++){
int x=s[i]-'a';
now=ch[now][x];
while(tail<=m&&my[tail].r==i){
int len=my[tail].r-my[tail].l+1;
int where=go(now,len);
int aa=query(rt[where],1,n,my[tail].k);
if(aa==-1)ans[my[tail].id]=-1;
else{
ans[my[tail].id]=aa-len+1;
}
tail++;
}
}
for(int i=1;i<=m;i++)cout<<ans[i]<<endl;
}
}S;
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
cin>>n;cin>>S.m;
cin>>s+1;
n=strlen(s+1);S.init();
for(int i=1;i<=n;i++)S.add(s[i]-'a',i);
S.Sort();
S.slove();
}
return 0;
}