题目描述
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
1.在文本文件WIR.IN中读入病毒代码;
2.判断是否存在一个无限长的安全代码;
3.将结果输出到文件WIR.OUT中。
输入输出格式
输入格式:
在文本文件WIR.IN的第一行包括一个整数n(n\le 2000)(n≤2000),表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
输出格式:
在文本文件WIR.OUT的第一行输出一个单词:
TAK——假如存在这样的代码;
NIE——如果不存在。
输入输出样例
输入样例#1: 复制
3 01 11 00000
输出样例#1: 复制
NIE
解题思路
先建立模式串的trie树,补全为trie图后,如果用一个无限长的字符串来匹配,且匹配不到任何一个模式串,除非这个trie图上有环,且在这个环中没有模式串的尾节点,且环中的节点的fail指针指向的节点也都不是模式串尾节点。
代码如下
#include <iostream>
#include <queue>
#include <cstring>
#define maxn 30005
using namespace std;
struct trie{
bool e;
int next[2];
int fail;
}tree[maxn];
int cnt;
void build(string str)
{
int now = 0;
for(int i = 0; i < str.size(); i ++){
if(!tree[now].next[str[i]-'0'])
tree[now].next[str[i]-'0'] = ++cnt;
now = tree[now].next[str[i]-'0'];
}
tree[now].e = true;
}
void get_fail()
{
queue<int> que;
for(int i = 0; i < 2; i ++){
if(tree[0].next[i]){
que.push(tree[0].next[i]);
tree[0].fail = 0;
}
}
while(!que.empty()){
int now = que.front();
que.pop();
for(int i = 0; i < 2; i ++){
if(tree[now].next[i]){
tree[tree[now].next[i]].fail = tree[tree[now].fail].next[i];
que.push(tree[now].next[i]);
if(tree[tree[tree[now].fail].next[i]].e) //fail指针指向一个尾节点
tree[tree[now].next[i]].e = true; //其后缀为病毒,所以也不行
}
else
tree[now].next[i] = tree[tree[now].fail].next[i];
}
}
}
bool vis[maxn]; //是否访问过
bool way[maxn]; //是否在此路径中
bool dfs(int x)
{
way[x] = true;
for(int i = 0; i < 2; i ++){
int r = tree[x].next[i];
if(way[r]){
return true;
}
else if(!tree[r].e && !vis[r]){
vis[x] = true;
if(dfs(r))
return true;
}
}
way[x] = false;
return false;
}
int main()
{
std::ios::sync_with_stdio(false);
int n;
cin >> n;
for(int i = 0; i < n; i ++){
string str;
cin >> str;
build(str);
}
get_fail();
if(dfs(0))
cout << "TAK" << endl;
else
cout << "NIE" << endl;
return 0;
}