传送门
这是一道练习AC自动机的好题.先来考虑一下题目特殊的读入方式.
如果把每个字符串存起来一个一个插入AC自动机的话这种做法最多只能过四个点.因为存储字符串的空间复杂度是O(|S|2)的.S的最大长度在1e5.这显然是不可接受的.
优化一下建Trie树的过程.可以观察到这种生成字符串的方式会产生很多重复的结点.所以我们直接在读入的时候跳Trie树的指针就可以了,在接收到B的时候跳回p的父亲节点就可以了.接收到正常字符就正常跳指针.
再来考虑一下怎么处理询问.先假定我们读入的字符串是正常一个一个读入的,那只需要遍历一遍y串,然后每次暴力跳fail指针打标记,最后看x的节点被打了几次标记就可以了.
这种方法显然太暴力了,我们知道暴力跳fail的过程都可以在fail指针上形成的树上转化成为树上的一些操作问题.如果对这种做法不了解的可以先去写一下洛谷上面的AC自动机(二次加强版).
所以我们考虑怎么在fail树上回答问题.还是假定一下读入的字符串是正常一个一个读入的.那么可以把暴力跳fail指针的过程转化为单点修改和子树求和的问题.只需要用dfs序+树状数组即可.如果这题不是这种特殊的读入方式的话,这种做法应该是可行的.复杂度是O(nlogn)的.
正解:可以观察到的是我们对fail树的修改其实是一个dfs的过程.而在每个询问里,是修改到y所在的结点停止.所以我们无需知道每一个字符串具体是怎么样的.只要把询问离线,给每个y在树上打标记,然后批量处理这个点的询问即可.这里需要搞清楚的是trie树和fail树这两颗树上操作的区别.以及各种映射之间的关系
参考代码:
#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#include <bits/stdc++.h>
using namespace std;
const int N = 4e5+10;
inline int read(){
int x = 0,f=1;char ch = getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
namespace Bit{ // 单点修改,区间求和
int n,c[N];
void add(int p,int v){
if(!p) return;
for(;p<=n;p+=p&-p) c[p] += v;
}
int ask(int p){
int res = 0;
for(;p;p-=p&-p) res += c[p];
return res;
}
int query(int l,int r){
return ask(r)-ask(l-1);
}
}
namespace Auto{
int trie[26][N],tot,p,fail[N],fa[N],cnt[N],bel[N],tt[26][N],ttot;
vector<pii> vec[N];
void bfs(){
queue<int> q;
fir(i,0,25) if(trie[i][0]) q.push(trie[i][0]);
while(q.size()){
int p = q.front(),fl = fail[p];
q.pop();
fir(i,0,25){
if(trie[i][p]) fail[trie[i][p]] = trie[i][fl],q.push(trie[i][p]);
else trie[i][p] = trie[i][fl];
}
}
}
int tim,dfn[N],nxt[N],head[N],to[N],ct,ed[N],ans[N];
void addedge(int x,int y){
nxt[++ct] = head[x];head[x] = ct;to[ct] = y;
}
void build(){
afir(i,tot,1) addedge(fail[i],i);
}
void dfs(int u){
dfn[u] = ++tim;
for(int i=head[u];i;i=nxt[i]){
int y = to[i];
dfs(y);
}
ed[u] = tim;
}
void solve(int u){
Bit::add(dfn[u],1);
for(auto x:vec[u])
ans[x.ft] = Bit::query(dfn[x.sd],ed[x.sd]);
fir(i,0,25){
if(tt[i][u])
solve(tt[i][u]);
}
Bit::add(dfn[u],-1);
}
}
string s[N];
using namespace Auto;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
string str;
cin >> str;
p = 0;
int n = 0;
for(auto x:str){
if(x == 'B')
p = fa[p];
else if(x == 'P') bel[++n] = p;
else{
if(!trie[x-'a'][p]){
fa[++tot] = p;
trie[x-'a'][p] = tot;
tt[x-'a'][p] = tot;
}
p = trie[x-'a'][p];
}
}
bfs();
build();
dfs(0);
Bit::n = tim;
int m;
cin >> m;
fir(i,1,m){
int x,y;
cin >> x >> y;
vec[bel[y]].pb(mpr(i,bel[x]));
}
Auto::solve(0);
fir(i,1,m) cout << ans[i] << "\n";
return 0;
}