题意:
给定 n n n 个字符串,产生方式为 ① 1 , c 1, c 1,c,表示 s i s_i si 是一个字符 c c c;② 2 , j , c 2, j, c 2,j,c,表示 s i s_i si 是 s j + c s_j + c sj+c。再有 m m m 次询问,每次询问给出 i , t i, t i,t,表示求串 t t t 在串 s i s_i si 中出现的次数。 ( n , m , ∑ ∣ s i ∣ , ∑ ∣ t i ∣ ≤ 4 × 1 0 5 ) (n, m, \sum |s_i|, \sum |t_i| \leq 4×10^5) (n,m,∑∣si∣,∑∣ti∣≤4×105)
链接:
https://codeforces.com/problemset/problem/1207/G
解题思路:
多模式串、多主串的匹配,显然主串 s i s_i si 需要用字典树存储,一方面可以考虑广义后缀自动机,以 s i s_i si 建立自动机,那么模式串 t i t_i ti 在自动机上找到对应结点,其 r i g h t right right 集合中在串 s i s_i si 中的 e n d p o s endpos endpos 数量即为答案,但是 r i g h t right right 集合总大小可以是 O ( n 2 ) O(n^2) O(n2) 的。反向考虑每个主串结点对模式串的贡献,那么就是跑主串,每次父链上的模式串计数 + 1 +1 +1 ,最后查询每个模式串对应结点的计数情况就是答案。
但是显然也不能直接枚举每个主串 s i s_i si 去自动机上跑,由于每个主串 s i s_i si 都是其字典树上的一条链,那么对字典树进行 d f s dfs dfs,每次到主串结束结点时统计它对相应的询问的模式串的贡献,回溯时撤回父链上的计数,那么只需要枚举字典树的结点数有关。
然后我们发现不如直接对模式串建立
A
C
AC
AC 自动机。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 4e5 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
char s[maxn];
vector<pii> P[maxn];
vector<int> G[maxn], qr[maxn];
int nxt[maxn][26], fail[maxn], pos[maxn];
int dfn[maxn], siz[maxn], c[maxn], ans[maxn];
int n, m, cnt, tim;
#define lowb(x) ((x)&(-x))
void update(int x, int val){
if(x <= 0) return;
while(x <= tim) c[x] += val, x += lowb(x);
}
int query(int x){
int ret = 0;
while(x) ret += c[x], x -= lowb(x);
return ret;
}
int add(){
++cnt; fail[cnt] = 0; return cnt;
}
void init(){
cnt = -1; add();
}
int insert(char *s){
int p = 0;
while(*s){
int t = *s - 'a';
if(!nxt[p][t]) nxt[p][t] = add();
p = nxt[p][t];
++s;
}
return p;
}
void cFail(){
queue<int> q;
for(int i = 0; i < 26; ++i) if(int v = nxt[0][i]) q.push(v);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = 0; i < 26; ++i){
if(int v = nxt[u][i]) fail[v] = nxt[fail[u]][i], q.push(v);
else nxt[u][i] = nxt[fail[u]][i];
}
}
}
void dfs(int u){
dfn[u] = ++tim, siz[u] = 1;
for(auto &v : G[u]){
dfs(v);
siz[u] += siz[v];
}
}
void build(){
cFail();
for(int i = 1; i <= cnt; ++i) G[fail[i]].pb(i);
dfs(0);
}
void dfs(int u, int p){
for(auto &e : P[u]){
int v = e.second, ch = e.first;
int np = nxt[p][ch - 'a'];
update(dfn[np], 1);
for(auto &id : qr[v]){
int x = pos[id];
int l = dfn[x], r = dfn[x] + siz[x] - 1;
ans[id] = query(r) - query(l - 1);
}
dfs(v, np);
update(dfn[np], -1);
}
}
int main() {
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 1; i <= n; ++i){
int opt, j; char ch[2]; cin >> opt;
if(opt == 1){
cin >> ch;
P[0].pb({ch[0], i});
}
else{
cin >> j >> ch;
P[j].pb({ch[0], i});
}
}
cin >> m;
init();
for(int i = 1; i <= m; ++i){
int p; cin >> p >> s;
pos[i] = insert(s);
qr[p].pb(i);
}
build();
dfs(0, 0);
for(int i = 1; i <= m; ++i){
cout << ans[i] << "\n";
}
return 0;
}