[POI2000]病毒
[POI2000] 病毒
1.题目
题目描述
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:请写一个程序:
1.在文本文件WIR.IN中读入病毒代码;
2.判断是否存在一个无限长的安全代码;
3.将结果输出到文件WIR.OUT中。
输入输出格式
输入格式:
在文本文件WIR.IN的第一行包括一个整数\(n(n \leqslant 2000)\) ,表示病毒代码段的数目。以下的\(n\)行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
输出格式:
在文本文件WIR.OUT的第一行输出一个单词:
TAK——假如存在这样的代码;
NIE——如果不存在。
输入输出样例
输入样例#1:
3 01 11 00000
输出样例#1:
NIE
2.题解
此题考虑将病毒串构建成一个AC自动机;病毒代码即会与某个串匹配,而安全代码则不会。所以构建好AC自动机后沿\(fail\)指针跳转,直到找到一个环即可。找环的操作可以使用dfs完成。
代码:
#include <algorithm>
#include <cstdio>
const int MAXN = 2010;
const int MAXLEN = 30010;
struct Node{
int fail;
int next[2];
bool exist;
}tree[MAXLEN];
struct QUEUE{
int head, tail;
int a[MAXLEN];
QUEUE() {head = tail = 0;}
bool empty() {return (head == tail);}
void push(int x) {a[++tail] = x;}
int pop() {return (a[++head]);}
void clean() {head = tail = 0;}
}que;
int n, top;
char str[MAXLEN];
bool vis[MAXLEN], nowvis[MAXLEN];
inline void insert() {
int iter(0), tmp(0), now(0);
for (; str[iter]; ++iter) {
tmp = str[iter] - '0';
if(!tree[now].next[tmp]) tree[now].next[tmp] = ++top;
now = tree[now].next[tmp];
}
tree[now].exist = true;
return ;
}
inline void Make_fail() {
int now(0), tmp(0);
if(tree[0].next[0]) tree[tree[0].next[0]].fail = 0, que.push(tree[0].next[0]);
if(tree[0].next[1]) tree[tree[0].next[1]].fail = 0, que.push(tree[0].next[1]);
while(!que.empty()) {
now = que.pop();
if((tmp = tree[now].next[0])) {
tree[tmp].fail = tree[tree[now].fail].next[0];
que.push(tmp);
if(tree[tree[tmp].fail].exist)
tree[tmp].exist = true;
}
else tree[now].next[0] = tree[tree[now].fail].next[0];
if((tmp = tree[now].next[1])) {
tree[tmp].fail = tree[tree[now].fail].next[1];
que.push(tmp);
if(tree[tree[tmp].fail].exist)
tree[tmp].exist = true;
}
else tree[now].next[1] = tree[tree[now].fail].next[1];
}
}
inline void dfs(int now) {
nowvis[now] = true; int tmp(0);
for (int i = 0; i <= 1; ++i) {
if(nowvis[(tmp = tree[now].next[i])]) {
printf("TAK\n"); std::exit(0);
}
else if(!tree[tmp].exist && !vis[tmp]) {
/*在这里,因为我们在求Fail指针的时候将此节点的空儿子修改为其Fail节点的儿子,即失配的时候一步即可转移到位,所以这里我们两个子节点都要dfs。这既保证了有Fail指针一定跳跃,也保证了它的实际子节点也可以搜索到。*/
vis[tmp] = true;
dfs(tmp);
}
}
nowvis[now] = false;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%s", str);
insert();
}
Make_fail();
dfs(0);
printf("NIE\n");
return 0;
}
附题目传送门:传送门