「
「
「字符串算法
」
」
」第
5
5
5章
A
C
AC
AC自动机
(
(
(后
2
2
2题
)
)
)
目录:
D.屏蔽词删除
E.病毒代码
D . D. D. 例题 4 4 4 屏蔽词删除
分析:
A
C
AC
AC自动机
+
+
+ 模拟
模 拟:
看文本串中 有没有模式串 有就直接抹掉 抹完
n
o
w
now
now要跳到文本串 从头开始到删除串 没有被抹 的最右端
(
(
(如样例
)
)
)
没有就找模式串就行了 至于删除 就是看标记 然后跳到前面没有标记的地方 标记的就是要删除的字符
其实这个删除部分可以写 栈
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<bitset>
#define Ctnue continue
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=1e5+5;
char s[N],ch[N];
int tot,n,rt[N],end[N];
bitset<N> k; //没啥用的bitset 标记
struct Trie{
int son[30],fail,size;
}trie[2*N];
queue<int> q;
void ins(char ovo[])
{
int len=strlen(ovo),now(0);
for(int i=0;i<len;++i)
{
int qwq=ovo[i]-'a';
if(!trie[now].son[qwq]) trie[now].son[qwq]=++tot;
now=trie[now].son[qwq];
}
trie[now].size=len;
}
void AC()
{
for(int i=0;i<26;++i)
if(trie[0].son[i])
{
q.push(trie[0].son[i]);
trie[trie[0].son[i]].fail=0;
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(trie[now].son[i])
{
q.push(trie[now].son[i]);
trie[trie[now].son[i]].fail=trie[trie[now].fail].son[i];
}
else trie[now].son[i]=trie[trie[now].fail].son[i];
}
}
}
void K(int r,int T) //暴力找标记 删除
{
int now=r;
while(T--)
{
while(k[r]) r=rt[r];
k[r]=1;
r--;
}
rt[now]=r;
}
void Delete(char ovo[])
{
int len=strlen(ovo),now=0;
for(int i=0;i<len;i++)
{
int qwq=ovo[i]-'a';
if(trie[now].son[qwq])
{
now=trie[now].son[qwq];
if(trie[now].size)
{
K(i,trie[now].size); //删除模式串
now=end[rt[i]]; //now回跳
}
}
else
{
while(!trie[trie[now].fail].son[qwq]&&now)
now=trie[now].fail;
if(!now) now=trie[now].son[qwq];
else now=trie[trie[now].fail].son[qwq]; //继续找模式串
}
end[i]=now;
}
}
int main(){
scanf("%s",&s);
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",&ch);
ins(ch);
}
AC();Delete(s);
int lenth=strlen(s);
for(int i=0;i<lenth;i++)
if(!k[i]) putchar(s[i]);
return 0;
}
E . E. E. 例题 5 5 5 病毒代码
分析:
先想想:如果构造出一个无限长的安全代码 再到
A
C
AC
AC自动机去匹配 会出现什么情况
?
?
?
那当一位位匹配时 永远不会跳到某个病毒代码的结尾位置 然后
A
C
AC
AC自动机就会一直原地
t
p
tp
tp(
先管病毒代码的结尾位置叫 危险标记 因为匹配到结尾已表明出现了病毒代码段
所以 问题就变成 在
t
r
i
e
trie
trie图上找一个环 并且环上没有危险标记
而且要注意 从根节点出发 不经过任何危险标记 也能到达这个环(不然
A
C
AC
AC自动机匹配啥
A
C
AC
AC自动机性质:如果节点
x
x
x的
f
a
i
l
fail
fail有危险标记 那这个点
x
x
x也是危险的 因为
f
a
i
l
fail
fail是
x
x
x的后缀
CODE:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<bitset>
#define Ctnue continue
//#pragma GCC optimize(2)
#define reg register
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
const int N=3e4+5;
struct Trie{
int fail,num[2];
bool in;
}trie[N];
int n,tot;
queue<int> q;
char ch[N];
bool vis[N],book[N];
void ins(char a[])
{
int len=strlen(a),now=0;
for(int i=0;i<len;i++)
{
int qwq=a[i]-'0';
if(!trie[now].num[qwq]) trie[now].num[qwq]=++tot;
now=trie[now].num[qwq];
}
trie[now].in=true;
}
void AC()
{
if(trie[0].num[0]>0) q.push(trie[0].num[0]);
if(trie[0].num[1]>0) q.push(trie[0].num[1]); //0或1 进队列
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<=1;i++)
{
if(trie[now].num[i]>0)
{
q.push(trie[now].num[i]);
int k=trie[now].fail;
while(k&&trie[k].num[i]==0) k=trie[k].fail; //最长后缀&&根节点
if(trie[k].num[i]==0) trie[trie[now].num[i]].fail=0; //fail转移到根节点
else{
trie[trie[now].num[i]].fail=trie[k].num[i];
if(trie[trie[k].num[i]].in) trie[trie[now].num[i]].in=1;
//自己的后缀危险 自己也危险
}
}else trie[now].num[i]=trie[trie[now].fail].num[i];
}
}
}
void dfs(int x)
{
vis[x]=1;
for(int i=0;i<=1;i++)
{
if(vis[trie[x].num[i]]){puts("TAK");exit(0);} //找到环了
else if(!trie[trie[x].num[i]].in&&!book[trie[x].num[i]])
{
book[trie[x].num[i]]=1;
dfs(trie[x].num[i]);
}
}
vis[x]=0;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",ch);
ins(ch);
}
AC();dfs(0);
puts("NIE");
return 0;
}