P3121

[USACO15FEB]Censoring G

建好自动机之后开个双栈。一个栈存留下来的字符,一个栈存自动机上经过的节点。

然后每当我们匹配的时候扫到一个完整串,我们就把这个串从整串中剔除。实现起来就是把第一个栈的栈顶往前移 $len$,然后把第二个栈的栈顶也往前移 $len$,然后第二个栈的栈顶就是现在所在的节点,继续匹配即可。

最后把第一个栈输出就是答案。

时间复杂度 $O(|s|+\sum_{i=1}^{n}|t_i|)$。复杂度不太高的原因是这里我们的指针真的回跳了。

代码:

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
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define ll long long
using namespace std;
namespace Ehnaev{
inline ll read() {
ll ret=0,f=1;char ch=getchar();
while(ch<48||ch>57) {if(ch==45) f=-f;ch=getchar();}
while(ch>=48&&ch<=57) {ret=(ret<<3)+(ret<<1)+ch-48;ch=getchar();}
return ret*f;
}
}using Ehnaev::read;

const ll N=1e6;

ll n,m,tot,top1,top2;
ll trie[N+5][26],cnt[N+5],st1[N+5],st2[N+5],nxt[N+5],lst[N+5];
char buft[N+5],s[N+5];
char *nowt=buft,*t[N+5];

inline void Ins(char *str) {
ll len=strlen(str),p=0;
for(ll k=0;k<len;k++) {
ll ch=str[k]-'a';if(!trie[p][ch]) trie[p][ch]=++tot;p=trie[p][ch];
}
cnt[p]=len;
}

inline ll Del(ll p) {top1-=cnt[p];top2-=cnt[p];return st2[top2];}

inline void AC_Pre() {
queue<ll> q;
for(ll c=0;c<26;c++) {
ll u=trie[0][c];if(u) {nxt[u]=0;lst[u]=0;q.push(u);}
}
while(!q.empty()) {
ll h=q.front();q.pop();
for(ll c=0;c<26;c++) {
ll u=trie[h][c];if(!u) {trie[h][c]=trie[nxt[h]][c];continue;}
q.push(u);ll v=nxt[h];while(v&&!trie[v][c]) v=nxt[v];
nxt[u]=trie[v][c];lst[u]=cnt[nxt[u]]?nxt[u]:lst[nxt[u]];
}
}
}

inline void AC() {
for(ll i=0,j=0;i<n;i++) {
ll c=s[i]-'a';j=trie[j][c];
st1[++top1]=c;st2[++top2]=j;
if(cnt[j]) {j=Del(j);}
else {if(lst[j]) j=Del(lst[j]);}
}
}

int main() {

scanf("%s",s);n=strlen(s);
m=read();
for(ll i=1;i<=m;i++) {
t[i]=nowt;scanf("%s",t[i]);nowt+=strlen(t[i])+2;Ins(t[i]);
}

AC_Pre();AC();

for(ll i=1;i<=top1;i++) {putchar(st1[i]+'a');}

return 0;
}