洛谷P5357,关于AC自动机的优化方案


【模板】AC 自动机(二次加强版)

题目传送门

题目大意

给你一个文本串 $S$ 和 $n$ 个模式串 $T_{1 \sim n}$ ,请你分别求出每个模式串 $T_i$ 在 $S$ 中出现的次数。

当时写题过程

MySubmissions

当时看到题目后,看着和前面一个模板题一模一样,于是,就直接把前面一个的代码 copy 了过来,交了一发,出现了第一幕,40分,里面不是 WA 就是 TLE ,我想不通为什么会有 WA ,于是,我又重新读了一遍题,发现这道题里面 数据不保证任意两个模式串不相同

我想先保证算法的正确性,然后再进行优化操作,于是,把这里改了一下之后,就又交了一发,果然,这一发里面就只有 TLE 了,那么问题来了,该怎么优化呢。

当时想到了在匹配的过程中不进行答案的汇总,而是只进行汇总 Trie 树里面的每个节点到过的次数,等匹配结束之后再总的进行答案的汇总。果然,这次 TLE 的测试点数就少了好多,但是仍然无法AC。

之后,我又想到了在 bfs() 中把每个 Trie 树的节点所对应的单词汇总,这样就不需要每次都往根部递归求值了,结果,测试点中终于没有 TLE 了,但是,又出现了三个 MLE ,每个 Trie 树节点存的单词数量太大了,这个方法也行不通。

最后,我就想,有没有一种可能,我可以记录他在 bfs() 的过程中的 bfs 序,然后,在匹配的过程中只记录每个节点到过的次数,最后,在匹配结束之后,利用 bfs 序的逆序进行遍历,每遍历一个点,就把这个点往根部递归的第一个点的访问次数加上这个点的访问次数,以此递推,即可得出答案数组。

思路

首先,在匹配的过程中,我们只需要记录每个点的访问次数,这里用 $nnum$ 数据进行记录。在匹配结束之后,为了防止递归式的求解(因为这样会一直重复访问一些(数据量非常大)节点),我们需要遍历一遍就能求解出答案的方法。于是,我们需要一个特殊的遍历序列,这个序列需要满足后遍历的节点在递归的思路里面不会到达先遍历的节点,而且先遍历的节点需要能够把他的访问次数传递到他在递归的思路中到达的后遍历的节点。刚好,我们在 bfs() 的过程的序列刚好就是这个序列的逆序列。于是,我们记录下来在 bfs() 过程中的访问顺序,在最后求解答案的时候再逆序访问即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<cstring>
#include<set>
#include<map>
#include<cmath>
#include<iostream>
#include<unordered_set>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Inf 0x3f3f3f3f
//#define int long long
using namespace std;
typedef long long LL;
typedef pair<int,int> P;
typedef pair<P,int> PP;
const int MAXX=2000005;
const double eps=0.0000001;
const LL mod=998244353;

const int id_max=MAXX;
int n,flag[id_max],fail[id_max];
int trie[id_max][26],id_cnt;
vector<int> id[id_max];
char s[MAXX];
int num[MAXX];

//初始化,用于多组数据输入
void init(){
id_cnt=0;
for(int i=0;i<id_max;++i){
flag[i]=fail[i]=0;
id[i].clear();
for(int j=0;j<26;++j)
trie[i][j]=0;
}
}

//向插入字典树中插入一个单词
void ins(char s[]){
int len=strlen(s+1);
int now=0;
for(int i=1;i<=len;++i){
if(trie[now][s[i]-'a']==0)
trie[now][s[i]-'a']=++id_cnt;
now=trie[now][s[i]-'a'];
}
++flag[now];
}

//向插入字典树中插入一个单词,并与输入顺序进行链接
void ins(char s[],int idd){
int len=strlen(s+1);
int now=0;
for(int i=1;i<=len;++i){
if(trie[now][s[i]-'a']==0)
trie[now][s[i]-'a']=++id_cnt;
now=trie[now][s[i]-'a'];
}
id[now].emplace_back(idd);
}

int bfsn[id_max];
//链接fail指针
void bfs(){
queue<int> qq;
for(int i=0;i<26;++i)
if(trie[0][i])
qq.emplace(trie[0][i]);
int bfs_cnt=0;
while(!qq.empty()){
int now=qq.front();qq.pop();
bfsn[++bfs_cnt]=now;
for(int i=0;i<26;++i){
if(trie[now][i]){
fail[trie[now][i]]=trie[fail[now]][i];
qq.emplace(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];
}
// int fa=fail[now];
// id[now].insert(id[now].end(),id[fa].begin(),id[fa].end());
}
}

//返回字典中有多少个单词在s串中出现过
int getone(char s[]){
int now=0,len=strlen(s+1);
int ret=0;
for(int i=1;i<=len;++i){
now=trie[now][s[i]-'a'];
int jj=now;
while(jj>0&&flag[jj]!=-1){
ret+=flag[jj];
flag[jj]=-1;
jj=fail[jj];
}
}
return ret;
}
int nnum[id_max];
//需事先定义num数组,用于保存每个单词在s串中出现过几次
void getnum(char s[]){
memset(num,0,sizeof(num));
int now=0,len=strlen(s+1);
for(int i=1;i<=len;++i){
now=trie[now][s[i]-'a'];
++nnum[now];
}
}

inline void solve_it(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
getchar();
scanf("%s",s+1);
ins(s,i);
}
bfs();
getchar();scanf("%s",s+1);

getnum(s);
for(int i=id_cnt;i>0;--i){
int now=bfsn[i];
nnum[fail[now]]+=nnum[now];
int si=id[now].size();
for(int j=0;j<si;++j)
num[id[now][j]]+=nnum[now];
}
for(int i=1;i<=n;++i){
printf("%d\n",num[i]);
}

// printf("%d\n",getone(s));
}

signed main(){
// LL T;scanf("%lld",&T);
// while(T--)
solve_it();

return 0;
}

总结

这道题的变数感觉好多,当时写完这道题之后我去翻了翻题解,发现并没有我这样的写法,题解中的写法各式各样,思路众多,以后写这类题目一定要从多个方面进行思考。


文章作者: Shaun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Shaun !
评论
  目录