这是一道充分利用ac自动机性质才能想出来的好题
题意:
有
n
n
n 条文本串,且文本串皆以拼接形式给出 有
m
m
m 条模式串 询问q次某一模式串在某一文本串中出现的次数
数据范围:
q
,
m
,
n
≤
4
×
1
0
5
q, m, n \leq 4\times 10^5
q , m , n ≤ 4 × 1 0 5
前置技能: trie树 + ac_automaton自动机
Tutorial: 拿到题目, 很自然的想到以模式串建立ac自动机, 以文本串建立trie树, 问题是怎么回答询问: 用dfs序录入trie树, 每次从当前的自动机节点顺着fail遍历回根节点, 将路遇的所有是模式串结尾的节点++, 代表以该节点结尾的模式串又多出现了一次, 其中last是fail的优化版本, 在建立自动机的过程中就把fail的路径压缩了一下, 使得路径变为线性长度. last的个点都是一个模式串的结尾, 返回的时候也要毁尸灭迹
详情参考
#include <bits/stdc++.h>
using namespace std;
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define _rev(i, a, b) for (int i = (a); i >= (b); --i)
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rof(i, a, b) for (int i = (a); i > (b); --i)
#define oo 0x3f3f3f3f
#define ll long long
#define db double
#define eps 1e-8
#define bin(x) cout << bitset<10>(x) << endl;
#define what_is(x) cerr << #x << " is " << x << endl;
#define met(a, b) memset(a, b, sizeof(a))
#define all(x) x.begin(), x.end()
#define pii pair<int, int>
const int maxn = 4e5 + 10 ;
string tmp;
struct Trie
{
int cnt, ch[ maxn] [ 26 ] ;
int init ( int o)
{
met ( ch[ o] , 0 ) ;
return o;
}
int insert ( char c, int th = 0 )
{
int & v = ch[ th] [ c - 'a' ] ;
if ( ! v)
v = init ( ++ cnt) ;
return v;
}
} trie;
int tr_node[ maxn] ;
int ac_node[ maxn] ;
vector< int > querys[ maxn] ;
struct AC_automaton
{
int last[ maxn] , end[ maxn] , fail[ maxn] , ch[ maxn] [ 26 ] , cnt, occ[ maxn] , ans[ maxn] ;
int init ( int o)
{
met ( ch[ o] , 0 ) ;
last[ o] = end[ o] = fail[ o] = 0 ;
return o;
}
int insert ( string& str, int th)
{
int cur = 0 ;
for ( auto i : str)
{
int & t = ch[ cur] [ i - 'a' ] ;
if ( ! t)
{
t = init ( ++ cnt) ;
}
cur = t;
}
end[ cur] = th;
return cur;
}
void get_fail ( )
{
queue< int > q;
_for ( i, 0 , 26 )
{
int & t = ch[ 0 ] [ i] ;
if ( t)
q. push ( t) ;
}
while ( ! q. empty ( ) )
{
int cur = q. front ( ) ;
q. pop ( ) ;
_for ( i, 0 , 26 )
{
int & son = ch[ cur] [ i] ;
if ( son)
{
q. push ( son) ;
fail[ son] = ch[ fail[ cur] ] [ i] ;
last[ son] = end[ fail[ son] ] ? fail[ son] : last[ fail[ son] ] ;
}
else
son = ch[ fail[ cur] ] [ i] ;
}
}
}
void dfs ( int trn, int acn) {
for ( int i = end[ acn] ? acn : last[ acn] ; i; i = last[ i] )
++ occ[ i] ;
for ( auto query_id : querys[ trn] )
ans[ query_id] = occ[ ac_node[ query_id] ] ;
_for ( i, 0 , 26 ) {
if ( trie. ch[ trn] [ i] ) {
dfs ( trie. ch[ trn] [ i] , ch[ acn] [ i] ) ;
}
}
for ( int i = end[ acn] ? acn : last[ acn] ; i ; i = last[ i] )
-- occ[ i] ;
}
void print ( int m) {
_rep ( i, 1 , m) {
cout << ans[ i] << " " << endl;
}
}
} automaton1;
signed main ( )
{
ios:: sync_with_stdio ( 0 ) ;
int n;
cin >> n;
_rep ( i, 1 , n)
{
int op;
char c;
cin >> op;
if ( op == 1 )
{
cin >> c;
tr_node[ i] = trie. insert ( c) ;
}
else
{
int th;
cin >> th >> c;
tr_node[ i] = trie. insert ( c , tr_node[ th] ) ;
}
}
int m;
cin >> m;
_rep ( i, 1 , m)
{
int th;
cin >> th >> tmp;
ac_node[ i] = automaton1. insert ( tmp, th) ;
querys[ tr_node[ th] ] . push_back ( i) ;
}
automaton1. get_fail ( ) ;
automaton1. dfs ( 0 , 0 ) ;
automaton1. print ( m) ;
}
本题的收获: last压缩路径, 在ac自动机上跑trie