给出一个
n
≤
1
e
5
n\leq1e5
n≤1e5的字典树,并且给出
n
≤
1
e
5
n\leq1e5
n≤1e5的字符串
s
s
s,每次询问给出一个区间
[
l
,
r
]
[l,r]
[l,r],然后让这个子串在字典树上沿边匹配。每次如果失配就会重新回到根的位置重新匹配。对于每次查询,求出失配的次数以及最终匹配到位置。
先考虑如何快速求出第一次失配的位置,对于每个后缀的开头,一定满足当前这个前缀如果不失配,那么更小的前缀也不会失配。所以可以二分前缀的长度。可以预处理dfs一遍字典树,用一个map存下来所有前缀值的哈希。这样最长的满足的前缀再
+
1
+1
+1就是最小的失配位置。
同样的方法可以处理出每个后缀作为开头失配的位置。
f
i
,
0
f_{i,0}
fi,0表示
i
i
i位置开始失配
1
1
1次的位置。然后倍增预处理出
f
i
,
j
f_{i,j}
fi,j表示
i
i
i开头失配
j
j
j次的位置。
对于每次查询直接倍增,最后的一段一定是一个前缀,用map保存点的信息。这样就做到单次询问
O
(
l
o
g
n
)
O(logn)
O(logn)了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e5+3;
char s[N];
struct edge { int v;char w;};
vector<edge> go[N];
unordered_map<ull,int> mp;
const int base=163;
unsigned long long h[N],p[N];
unsigned long long gethash(int l,int r) { return h[r]-h[l]*p[r-l]; }
void init() {
p[0]=1;
for(int i=1;i<=100000;i++)
p[i]=p[i-1]*base;
}
void dfs(int u,ull hs) {
mp[hs]=u;
for(auto &e:go[u]) {
int v=e.v;
dfs(v,hs*base+e.w);
}
}
int f[N][22];
int main() {
init();
int T;
scanf("%d",&T);
while(T--) {
int n,m,q;
mp.clear();
scanf("%d%d%d",&n,&m,&q);
for(int i=0;i<=n;i++)
go[i].clear();
for(int i=1;i<=n;i++) {
int fa;char w[3];
scanf("%d%s",&fa,w);
go[fa].push_back({i,w[0]});
}
scanf("%s",s+1);
for(int i=1;i<=m;i++)
h[i]=h[i-1]*base+s[i];
int lg=0;
while((1<<lg)<=m) {
lg++;
}
dfs(0,0);
for(int i=0;i<=m;i++) {
int l=0,r=m-i;
int ans=0;
while(l<=r) {
int mid=(l+r)>>1;
if(mp.count(gethash(i,i+mid))) ans=mid,l=mid+1;
else r=mid-1;
}
f[i][0]=i+ans+1;
}
f[m+1][0]=m+1;
for(int i=1;i<lg;i++) {
for(int j=0;j<=m+1;j++) {
f[j][i]=f[f[j][i-1]][i-1];
}
}
while(q--) {
int l,r;
scanf("%d%d",&l,&r);
int ans=0;l=l-1;
for(int i=lg-1;i>=0;i--) {
if(f[l][i]<=r) {
l=f[l][i];
ans+=(1<<i);
}
}
int id=mp[gethash(l,r)];
printf("%d %d\n",ans,id);
}
}
return 0;
}