[BZOJ2938]:[Poi2000]病毒(AC自动机)
题目传送门
题目描述
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
请写一个程序,读入病毒代码,判断是否存在一个无限长的安全代码,将结果输出。
输入格式
第一行包括一个整数n,表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。
输出格式
第一行输出一个单词。假如存在这样的代码,则输出
TAK
,否则输出NIE
。样例
样例输入:
3
01
11
00000
01
11
00000
样例输出:
NIE
数据范围与提示
对于全部数据,所有病毒代码段的总长度不超过3×104。
题解
一看是多模式串,首先应该想到是AC自动机。
如果还不会AC自动机,可以转到这篇博客,个人感觉还是写的挺清楚的:AC自动机讲解+[HDU2222]:Keywords Search(AC自动机)。
那么我们考虑怎么去处理。
显然如果存在这么一个无限长的安全串的话,说明它在AC自动机里一直匹配不上,也就是说我们需要找到一个环,这个环中的每一个点都没有任何串在这个点结束。
首先,将所有的文字段压入AC自动机,然后搞Fail指针,基本操作,不再赘述。
end数组的含义为:第i个点有没有串在这里结束,有则为1,没有则为0。
不过需要注意的是,如果一个点的Fail指针指向的点的end为1,那么它也要为1,因为如果它的Fail指针指向的点的end为1的话,说明从根结点到Fail所组成的串是从跟节点到它所组成的串的后缀。
这些都处理好之后,我们就看着是不是一个环就好了。
代码时刻
#include<bits/stdc++.h>
using namespace std;
int trie[30001][2],cnt=1;
char s[30001];
bool end[30001],flag[30001],vis[30001];
int nxt[30001],que[30001];
void insert(char *str)//构建Trie树
{
int p=1;
int len=strlen(str);
for(int i=0;i<len;i++)
{
int ch=str[i]-'0';
if(!trie[p][ch])trie[p][ch]=++cnt;
p=trie[p][ch];
}
end[p]=1;//结尾点标记为1
}
void build()
{
for(int i=0;i<2;i++)
trie[0][i]=1;
que[1]=1;
for(int head=1,tail=1;head<=tail;head++)
for(int i=0;i<2;i++)
if(!trie[que[head]][i])trie[que[head]][i]=trie[nxt[que[head]]][i];
else
{
que[++tail]=trie[que[head]][i];
nxt[trie[que[head]][i]]=trie[nxt[que[head]]][i];
end[trie[que[head]][i]]|=end[nxt[trie[que[head]][i]]];//如果end[nxt[x]]的话,说明root->x是root->fail后缀。
}
}
bool find(int x)
{
vis[x]=1;
for(int i=0;i<2;i++)
{
if(vis[trie[x][i]])return 1;//如果这个点被访问过了,那么说明出现了一个环。
if(end[trie[x][i]]||flag[trie[x][i]])continue;//发现这条路走不下去(end为1),或者是已经被走过了(flag为1)。
flag[trie[x][i]]=1;//标记已经走过了这个点。
if(find(trie[x][i]))return 1;//接着走下一个点。
}
vis[x]=0;
return 0;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",s);
insert(s);
}
build();
if(find(0))printf("TAK");
else printf("NIE");
return 0;
}
rp++