[POI2000]病毒
题目描述
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
1.在文本文件WIR.IN中读入病毒代码;
2.判断是否存在一个无限长的安全代码;
3.将结果输出到文件WIR.OUT中。
输入格式:
在文本文件WIR.IN的第一行包括一个整数n(
n
≤2000),表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
输出格式:
在文本文件WIR.OUT的第一行输出一个单词:
TAK——假如存在这样的代码;
NIE——如果不存在。
输入样例#1:
301 11 00000
输出样例#1:
NIE
题解:
转化不太好想,对ac自动机的理解要求挺高的。
若存在无线长的代码,一定存在在ac自动机上能已知匹配且无法遇到is_end节点的字符串。
那么就可以构造出ac自动机然后判断有没有环即可。
对于fail指针指向的is_end节点,一定是这个字符串的后缀,也不能选。
代码:
#include<bits/stdc++.h>
using namespace std;
const int max_n = 30010;
const int inf = 1e9+7;
int ch[max_n][2],fail[max_n];
bool vis[max_n],is_end[max_n],flag;
char s[max_n];
int n,tot,sz;
inline void insert()
{
int len=strlen(s),now=0;
for(int i=0; i<len; ++i)
{
int x=s[i]-'0';
if(!ch[now][x]) ch[now][x]=++tot;
now=ch[now][x];
}
is_end[now]=true;
}
inline void make_fail()
{
queue<int> q;
for(int i=0; i<=1; ++i)
if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty())
{
int now=q.front(); q.pop();
for(int i=0; i<=1; ++i)
{
if(!ch[now][i])
{
ch[now][i]=ch[fail[now]][i];
continue;
}
fail[ch[now][i]]=ch[fail[now]][i];
if(is_end[fail[ch[now][i]]]) is_end[ch[now][i]]=true;
q.push(ch[now][i]);
}
}
}
inline void dfs(int now)
{
vis[now]=true;
for(int i=0; i<2; ++i)
if(!is_end[ch[now][i]])
{
if(vis[ch[now][i]])
{
flag=true;
return;
}
vis[ch[now][i]]=true;
dfs(ch[now][i]);
if(flag) return;
vis[ch[now][i]]=false;
}
}
int main()
{
// freopen("wir.in","r",stdin);
// freopen("wir.out","w",stdout);
scanf("%d\n",&n);
for(int i=1; i<=n; ++i)
{
gets(s);
insert();
}
make_fail();
dfs(0);
if(flag) puts("TAK");
else puts("NIE");
return 0;
}