Description
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
l 读入病毒代码;
l 判断是否存在一个无限长的安全代码;
l 将结果输出
Input
第一行包括一个整数n,表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
Output
你应在在文本文件WIN.OUT的第一行输出一个单词:
l TAK——假如存在这样的代码;
l NIE——如果不存在。
Sample Input
3
01
11
00000
Sample Output
NIE
首先在AC自动机上建出所有的01字串。那么关键就在于在AC自动机上如何跑才能验证是否存在。先然我们可以进行DFS,一直往0和1两个方向进行DFS,如果发现往某一边走会走到某一个字串的尾部,就不往那边走。
这样一直DFS,如果能找到我们原来经过的点,那也就形成了一个环,可以无限重复下去,那也就是符合题目要求的循环节。于是输出TAK。
这样理论上就可以了,但是在大数据并且最终答案是NIE的数据时会T。我们可以再考虑当前DFS的性质:每个点只能经过一次,无论其能不能形成一个环。于是我们再开一个数组记录点是否被经过,这个数组的修改与上面找循环节的vis数组几乎相同,只是在回溯的时候不清零。(我的代码中为ask数组)
#include<bits/stdc++.h>
#define MAXN 30005
using namespace std;
int read(){
char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,cnt=1,flag,nxt[MAXN][2],fail[MAXN],f[MAXN],q[MAXN],vis[MAXN],ask[MAXN];
char s[MAXN];
void build(){
int pl=1,l=strlen(s+1);
for(int i=1;i<=l;i++){
int x=s[i]-'0';
if(nxt[pl][x]) pl=nxt[pl][x];
else{
++cnt;nxt[pl][x]=cnt;pl=cnt;
}
}
f[pl]=1;
}
void getfail(){
int h=0,t=0;
for(int i=0;i<2;i++) if(nxt[1][i]) fail[q[++t]=nxt[1][i]]=1;
while(h<t){
int now=q[++h];
for(int i=0;i<2;i++){
if(nxt[now][i]){
fail[q[++t]=nxt[now][i]]=nxt[fail[now]][i];
f[nxt[now][i]]|=f[nxt[fail[now]][i]];
if(!fail[nxt[now][i]]) fail[nxt[now][i]]=1;
}
else nxt[now][i]=nxt[fail[now]][i];
}
}
}
void dfs(int x){
if(flag) return;
if(vis[x]){
flag|=1;return;
}
vis[x]=1;ask[x]=1;
for(int i=0;i<2;i++){
if(!f[nxt[x][i]]&&(!ask[nxt[x][i]]||vis[nxt[x][i]])) dfs(nxt[x][i]);
if(flag) return;
}
vis[x]=0;
}
int main()
{
n=read();
for(int i=1;i<=n;i++){
scanf("%s",s+1);
build();
}
getfail();
if(nxt[1][0]) dfs(nxt[1][0]);
memset(vis,0,sizeof(vis));
if(nxt[1][1]) dfs(nxt[1][1]);
puts(flag?"TAK":"NIE");
return 0;
}