题意
给一个长度为n的字符串,Q次询问,每次询问\((l,r,k)\) , 回答子串\(s_ls_{l+1}\cdots s_r\) 第\(k\) 次出现的位置,若不存在输出-1。\(n\le 1e5,Q\le 1e5\)
分析
查询子串第 k 次出现的位置,很容易想到要用处理字符串的有力工具——后缀数组。
那么该怎么用呢?我们先把样例的字符串的每个后缀排个序,然后对样例进行模拟
原串:aaabaabaaaab
排名 | 后缀 | 位置 |
---|---|---|
1 | aaaab | 8 |
2 | aaab | 9 |
3 | aaabaabaaab | 1 |
4 | aab | 10 |
5 | aabaaaab | 5 |
6 | aabaabaaab | 2 |
7 | ab | 11 |
8 | abaaaab | 6 |
9 | abaabaaaab | 3 |
10 | b | 12 |
11 | baaaab | 7 |
12 | baabaaaab | 4 |
查询:[3,3], k = 4
[3,3]表示子串为 \(a\) ,我们可以找到起始位置为 3 的后缀 \(t = abaabaaab\) ,该后缀的第一个字符代表了当前要查询的子串,惊奇的发现,该子串又同时出现在了其他的一些后缀中,而这些后缀与\(t\) 的LCP(最长公共前缀)大于等于 1 。在这个例子中我们可以发现排名在9之前的后缀与 t 的LCP都大于1,所以只需要在这些后缀的开始位置中找第 k 大的即可。也就是在[8,9,1,10,5,2,11,6,3]
中找第 4 大,即 5.
查询:[2,3], k = 2
[2,3] 表示子串为\(aa\), 起始位置为2的后缀\(t = aabaabaaab\) , 与 \(t\) LCP 大于等于2的后缀的开始位置有[8,9,1,10,5,2]
, 第2大的位置就是2。
那么怎么体现在程序中呢?
求出后缀数组的 \(rank,height\) 数组,利用\(ST\)表可以\(O(1)\) 查询两个后缀的LCP。
另外可以发现在后缀排名中,排名为 x 的后缀与其他后缀的LCP随着排名之差绝对值增大而减小,所以可以两次二分在排名中找到一个区间,使得这个区间内的所有后缀与目标后缀的LCP都大于等于查询的子串的长度。
找到这个区间之后,利用可持久化线段树找第 k 大值(对于sa数组)即可
复杂度分析:求后缀数组\(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; }