给一个长为n的字符串,m次询问,每次求子串[l,r]第k次出现的起点位置
做法:
数据量很大,输入的字符串/询问总量可以达到1e5*5,必须尽量实现单次$O(logn)$的查询和至多$O(nlogn)$的预处理
1.子串[l,r]一定是某个后缀的前缀,而"后缀的前缀的重复出现"这个问题可以很容易想到后缀数组的height
2.考虑重复出现,显然一个后缀的长为r-l+1的前缀的出现位置即为height数组上一段连续的大于等于r-l+1的区间
3.得到这个区间后,就变成了一个在sa数组上求区间第k小的问题了
那么做法就是:
预处理:
1.预处理height数组
2.预处理height数组区间min的st表
3.预处理支持查询sa数组上的区间kth的一棵可持久化线段树
处理询问:
1.我们从后缀数组的rk[l],直接锁定一个合法的起始位置,
2.然后利用st表快速二分出一个合法的连续区间,满足min>=r-l+1,
3.在可持久化线段树上查询这个区间的kth小
总复杂度:$O((m+n)logn)$
(说起来挺复杂,但是也就130行)
#include<bits/stdc++.h> #define ll long long #define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) #define rep(ii,a,b) for(int ii=a;ii<=b;++ii) #define per(ii,a,b) for(int ii=b;ii>=a;--ii) using namespace std;//head const int maxn=1e6+10,maxm=3e5+10; const ll INF=0x3f3f3f3f,mod=1e9+7; int casn,n,m,k; const int csize=128; char s[maxn]; namespace suffix{ int sa[maxn],h[maxn],rank[maxn]; int x[maxn],y[maxn],c[maxn]; void geth(int n){ int j,k=0; rep(i,1,n)rank[sa[i]]=i; rep(i,1,n){ if(k)k--; j=sa[rank[i]-1]; while(s[i+k]==s[j+k])k++; h[rank[i]]=k; } } void getsa(int n,int m){ rep(i,1,m)c[i]=0; rep(i,1,n)c[x[i]=s[i]]++; rep(i,1,m)c[i]+=c[i-1]; per(i,1,n)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int p=1; rep(i,n-k+1,n)y[p++]=i; rep(i,1,n) if(sa[i]>=k+1)y[p++]=sa[i]-k; rep(i,1,m)c[i]=0; rep(i,1,n)c[x[y[i]]]++; rep(i,1,m)c[i]+=c[i-1]; per(i,1,n)sa[c[x[y[i]]]--]=y[i]; swap(x,y); p=1;x[sa[1]]=1; rep(i,2,n) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p:++p; if(p>=n)break; m=p; } geth(n); } } const int maxp=20; class stable{public: int logn[maxn],dp[maxp][maxn]; void init(int n=maxn-1){ logn[2]=1; rep(i,3,n) logn[i]=logn[i>>1]+1; } void cal(int *a,int n){//init(n) rep(i,1,n) dp[0][i]=a[i]; rep(j,1,maxp) for(int i=1;i+(1<<j)-1<=n;++i) dp[j][i]=min(dp[j-1][i],dp[j-1][i+(1<<(j-1))]); } inline int query(int l,int r){ int lg=logn[r-l+1]; return min(dp[lg][l],dp[lg][r-(1<<lg)+1]); } }st; namespace tree{ #define nd seg[now] #define ndp seg[pre] #define mid ((s+t)>>1) int rt[maxn],size; struct node{int l,r,sum;}seg[maxn*30]; void init(){ size=0;fill_n(rt,n+1,0); } void maketree(int s=1,int t=n,int &now=rt[0]){ now=++size;nd={s,t,0}; if(s==t) return ; maketree(s,mid,nd.l);maketree(mid+1,t,nd.r); } void update(int &now,int pre,int k,int s=1,int t=n){ now=++size;nd=ndp,nd.sum++; if(s==t) return ; if(k<=mid)update(nd.l,ndp.l,k,s,mid); else update(nd.r,ndp.r,k,mid+1,t); } int query(int now,int pre,int k,int s=1,int t=n){ if(s==t) return s; int sum=seg[ndp.l].sum-seg[nd.l].sum; if(k<=sum) return query(nd.l,ndp.l,k,s,mid); else return query(nd.r,ndp.r,k-sum,mid+1,t); } inline int kth(int l,int r,int k){ return query(rt[l-1],rt[r],k); } inline void up(int a,int x){ update(rt[a],rt[a-1],x); } #undef mid }; int query(int s,int t,int k){ int pos=suffix::rank[s]; int l=1,r=pos-1,len=t-s+1; int s0=pos,t0=pos; while(l<=r){ int mid=(l+r)>>1; if(st.query(mid+1,pos)>=len) s0=mid,r=mid-1; else l=mid+1; } l=pos+1,r=n; while(l<=r){ int mid=(l+r)>>1; if(st.query(pos+1,mid)>=len) t0=mid,l=mid+1; else r=mid-1; } if(t0-s0+1<k) return -1; else return tree::kth(s0,t0,k); } int main() {IO; cin>>casn; st.init(); while(casn--){ cin>>n>>m>>(s+1); suffix::getsa(n,128); st.cal(suffix::h,n); tree::init(); tree::maketree(1,n); rep(i,1,n) tree::up(i,suffix::sa[i]); while(m--) { int l,r,k;cin>>l>>r>>k; cout<<query(l,r,k)<<endl; } } }