题目给定一个字符串,若为小写字母则表示加入该字符。若为 P P P,则打印当前字符串,并换行(打印后当前字符串不变)。若为 B B B ,则删除当前字符串的最后一位。给定 m m m 次查询,问你第 x x x 次打印的字符串在第 y y y 次中出现了几次。
首先我们清楚一点,在 A C AC AC 自动机中的 f a i l fail fail 树上,当 a a a 可以通过 f a i l fail fail 指针跳转到 b b b,则表示以 b b b 结尾的串是以 a a a 结尾的串的后缀。所以当我们用 f a i l fail fail 指针建树时,每个结点的子树大小就是后缀为以该结点为结尾的字符串的字符串数量。
针对这题,首先,输入的字符串的前缀相同,所以我们考虑记录一个父亲结点,当输入为 B B B 的时候,就跳转至父亲结点。其余时候输入字符则直接加入到当前字典树中。
然后我们要把 f a i l fail fail 树建出来,计算出 d f s dfs dfs 序,这样可以把求解 x x x 的子树中出现了多少 y y y 串的前缀的问题(就是 x x x 出现了多少次),转换成区间求和问题。
这题查询很多,但是在利用 D F S DFS DFS 序求区间和的时候,本质是字符串利用其前缀的信息(在 x x x 的子树 D F S DFS DFS 序中出现了多少该字符串的前缀,也就是该字符串出现了多少次 x x x。),我们其实可以在一遍 D F S DFS DFS 中就求出所有字符串的包含 x x x 的数量,所以我们离线查询。
说实话很巧妙,做完这题对
A
C
AC
AC 自动机的
f
a
i
l
fail
fail 树理解又上了一个台阶。虽然前面还有几万阶。
代码如下:
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 5;
int cnt, tol, xu;
int fail[maxn], tr[maxn][26], trb[maxn][26], fa[maxn], tend[maxn], tree[maxn];
int ans[maxn], id[maxn], head[maxn], first[maxn], ver[maxn];
bool isq[maxn];
char input[maxn], text[maxn];
pair<int, int> qnum[maxn];
struct FailTree{
int v, next;
}edge[maxn];
struct node{
int id, x, y;
bool operator < (const node &a)const{
return y < a.y;
}
}qu[maxn];
void add(int u, int v){
edge[tol].v = v;
edge[tol].next = head[u];
head[u] = tol++;
}
void build(){
queue<int> q;
for(int i = 0; i < 26; i++) if(tr[0][i]) q.push(tr[0][i]);
while(!q.empty()){
int k = q.front();
q.pop();
for(int i = 0; i < 26; i++){
if(tr[k][i]){
fail[tr[k][i]] = tr[fail[k]][i];
q.push(tr[k][i]);
}else tr[k][i] = tr[fail[k]][i];
}
}
}
int lowbit(int x){return x&(-x);}
void addnums(int x, int w){
while(x <= xu){
tree[x] += w;
x += lowbit(x);
}
}
int getsum(int x){
int sum = 0;
while(x){
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
void dfs(int u){
first[u] = ++xu;
for(int k = head[u]; ~k; k = edge[k].next)dfs(edge[k].v); // 利用fail树的dfs序,就可以通过求区间和求出fail树的子树出现x的次数。
ver[u] = xu;
}
void DFS(int u){
addnums(first[u], 1);
if(isq[u])
for(int i = qnum[id[u]].first; i <= qnum[id[u]].second; i++)
ans[qu[i].id] = getsum(ver[tend[qu[i].x]]) - getsum(first[tend[qu[i].x]] - 1); // 将子树维护成链,区间区间求和就是答案
for(int i = 0; i < 26; i++) // 这里必须用字典树,因为按照题目意思,要统计字符串y中x作为子串出现的次数
if(trb[u][i])
DFS(trb[u][i]);
addnums(first[u], -1);
}
int main(){
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
int pnums = 0, m, p = 0, k;
cin >> input;
cin >> m;
for(int i = 0; i < m; i++){
qu[i].id = i;
cin >> qu[i].x >> qu[i].y;
}
sort(qu, qu + m);
for(int i = 0; input[i]; i++){ // 读入优化,因为插入字符串只涉及后缀的修改
if(input[i] == 'P'){
tend[++pnums] = p;
id[p] = pnums;
}else if(input[i] == 'B')
p = fa[p];
else{
k = input[i] - 'a';
if(!tr[p][k])
tr[p][k] = trb[p][k] = ++cnt; // trb 备份字典树
fa[tr[p][k]] = p;
p = tr[p][k];
}
}
build();
memset(head, -1, sizeof(head));
for(int i = 1; i <= cnt; i++)add(fail[i], i);
for(int i = 0 ; i < m; i++){
isq[tend[qu[i].y]] = true;
qnum[qu[i].y].first = i;
while(i + 1 < m && qu[i + 1].y == qu[i].y) i++;
qnum[qu[i].y].second = i;
}
dfs(0);
DFS(0);
for(int i = 0; i < m; i++)
cout << ans[i] << endl;
}