問題の意味
長さnの文字列、Q問合せ回、各クエリ所与\((L、R&LT、K)\) 、回答ストリングの\(s_ls_ {L + 1} \ cdots S_R \) の\(K \)位置の発生、そこに出力すれば-1。\(nはル1E5、Q \ \ル1E5 \)
分析
ストリングのk番目の位置を発見現れ、文字列処理を使用するための強力なツールを考えるのは簡単です - 接尾辞配列。
それでは、どのようにそれを使用するには?我々は最初のサフィックス文字列のサンプル、そしてサンプルがシミュレートされたシーケンスの各行
原串:aaabaabaaaab
ランキング | サフィックス | 場所 |
---|---|---|
1 | aaaab | 8 |
2 | AAAS | 9 |
3 | aaabaabaaab | 1 |
4 | AAB | 10 |
5 | aabaaaab | 5 |
6 | aabaabaaab | 2 |
7 | から | 11 |
8 | abaaaab | 6 |
9 | Ababaab | 3 |
10 | B | 12 |
11 | baaaab | 7 |
12 | Babaab | 4 |
クエリー:[3,3]、K = 4
[3,3]サブストリング表す\(\)を、我々は3サフィックスの開始位置を見つけることができる\(T = abaabaaab \) 、サブサフィックスの最初の文字が照会される電流、驚きを表しストリングは、いくつかの他のサフィックス、これらのサフィックスに登場したが、見つかった\(T \) LCP(最長の共通接頭辞)の1以上です。この例では、1より大きい9前のランキングのLCPサフィックストンで見つけることができるので、唯一のサフィックスの先頭にK-大を見つける必要があります。つまり、[8,9,1,10,5,2,11,6,3]
つまり5、第4位を見つけること。
クエリー:[2,3]、K = 2
[2,3]サブストリングである\(AAの\) 、サフィックスの開始位置2 \(T = aabaabaaab \) 、および\(T \) LCPは2に等しい拡張開始位置よりも大きいされ[8,9,1,10,5,2]
、第二位場所は2です。
それでは、どのプログラムで行う反映されていますか?
決定された接尾辞配列\(ランク、高\)使用して配列、\(ST \)とすることができるテーブルを\(O(1)\)クエリLCP 2つのサフィックス。
さらなる拡張がランキングで見つけることができ、ランクは、ランクの差分絶対値が小さくなるように、この範囲内のすべてのサフィックスように、2つの半分の区間順位を求めることができる他のサフィックスLCP X増加の接尾辞でありますLCPとターゲットは接尾語の長さは、サブストリングクエリに等しいより大きい。
この間隔を発見した後、ツリーラインの持続的な使用は、(SA配列用)は、k番目の最大値を見つけるために
複雑分析:接尾辞配列をシーク\(O(nlog(n)の )\) 、半分\(O(nlog(n)の )\) 、ツリークエリk番目の最大値の会長\(O(nlog(n)の )\)
全体的に複雑\(O(nlog(n)を )\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int MAXN = N;
char s[N];
int sa[N],x[N],y[N],c[N],rk[N],h[N],n,q;
int len, cnt;
int a[MAXN];
int b[MAXN];
int t[MAXN];
int ls[MAXN * 40];
int rs[MAXN * 40];
int sum[MAXN * 40];
int build(int l, int r) {
int rt = ++cnt;
int mid = l + r >> 1;
sum[rt] = 0;
if(l < r) {
ls[rt] = build(l, mid);
rs[rt] = build(mid + 1, r);
}
return rt;
}
int add(int o, int l, int r, int k) {
int rt = ++cnt;
int mid = l + r >> 1;
ls[rt] = ls[o]; rs[rt] = rs[o]; sum[rt] = sum[o] + 1;
if(l < r)
if(k <= mid) ls[rt] = add(ls[o], l, mid, k);
else rs[rt] = add(rs[o], mid + 1, r, k);
return rt;
}
int query(int ql, int qr, int l, int r, int k) {
int x = sum[ls[qr]] - sum[ls[ql]];
int mid = l + r >> 1;
if(l == r) return l;
if(x >= k) return query(ls[ql], ls[qr], l, mid, k);
else return query(rs[ql], rs[qr], mid + 1, r, k - x);
}
void build_sa(char *s,int n,int m){
memset(c,0,sizeof c);
for(int i=1;i<=n;++i) ++c[x[i] = s[i]];
for(int i=2;i<=m;++i) c[i] += c[i-1];
for(int i=n;i>=1;--i) sa[c[x[i]]--] = i;
for(int k=1;k<=n;k<<=1){
int p = 0;
for(int i=n-k+1;i<=n;++i) y[++p] = i;
for(int i=1;i<=n;++i) if(sa[i] > k) y[++p] = sa[i]-k;
for(int i=1;i<=m;++i) c[i] = 0;
for(int i=1;i<=n;++i) ++c[x[i]];
for(int i=2;i<=m;++i) c[i] += c[i-1];
for(int i=n;i>=1;--i) sa[c[x[y[i]]]--] = y[i] , y[i] = 0;
swap(x,y);
x[sa[1]] = 1; p = 1;
for(int i=1;i<=n;++i)
x[sa[i]] = (y[sa[i]] == y[sa[i-1]] && y[sa[i] + k] == y[sa[i-1]+k] ? p : ++p);
if(p >= n)break;
m = p;
}
}
void get_height(){
int k = 0;
for(int i=1;i<=n;++i)rk[sa[i]] = i;
for(int i=1;i<=n;++i){
if(rk[i] == 1)continue;
if(k) --k;
int j = sa[rk[i]-1];
while(j + k <= n && i + k <= n && s[i+k] == s[j+k])++k;
h[rk[i]] = k;
}
}
int mm[N];
int best[20][N];
void initRMQ(int n){
mm[0] = -1;
for(int i=1;i<=n;i++)
mm[i] = ((i & (i-1)) == 0) ? mm[i-1] + 1 : mm[i-1];
for(int i=1;i<=n;i++)best[0][i] = i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a = best[i-1][j];
int b = best[i-1][j+(1<<(i-1))];
if(h[a] < h[b])best[i][j] = a;
else best[i][j] = b;
}
}
int askRMQ(int a,int b){
int t = mm[b-a+1];
b -= (1<<t) - 1;
a = best[t][a];b = best[t][b];
return h[a] < h[b] ? a : b;
}
int lcp(int a,int b){
if(a == b)return n;
if(a > b)swap(a,b);
return h[askRMQ(a+1,b)];
}
int getL(int l,int r,int len,int x){
while(l < r){
int mid = l + r >> 1;
if(lcp(mid,x) < len) l = mid + 1;
else r = mid;
}
return l;
}
int getR(int l,int r,int len,int x){
while(l < r){
int mid = (l + r + 1) >> 1;
if(lcp(mid,x) < len) r = mid - 1;
else l = mid;
}
return l;
}
int getAns(int l,int r,int k){
return query(t[l - 1], t[r], 1, n, k);
}
int solve(int l,int r,int k){
int len = r - l + 1;
int L = getL(1,rk[l],len,rk[l]);//二分找区间左端点
int R = getR(rk[l],n,len,rk[l]);//二分找区间右端点
if(k > R-L+1) return -1;
return getAns(L,R,k);//返回主席树查询结果
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&q);
scanf("%s",s+1);
build_sa(s,n,150);
get_height();
initRMQ(n);
//初始化主席树
cnt = 0;
t[0] = build(1,n);
for(int i=1;i<=n;i++){
int tt = sa[i];
t[i] = add(t[i-1],1,n,tt);
}
while(q --){
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",solve(l,r,k));
}
}
return 0;
}