题意
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{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——如果不存在。
题解
很容易想到将病毒串放入Trie树,补全后,如果能找到一个不经过任何串末尾的环就有一个安全代码
考虑如何找环,本来想的是拓扑排序,不过就算找出一个环,也很难判断是否含末尾点
然后又不小心听到了dfs,想了想很有道理,于是写出了如下东西
结果WA了一片,第一个样例就错了.....
那考虑为什么会这样,按照上面的写法,下个点访问过一定是环吗?
不一定,补全后就是一个有向图,所以我们拿有向图举例
如果先从1遍历完,然后回到1,再从2到1时,就会判断是环
然而不是,因为这是有向图,如果是无向图的话确实有环。
那么考虑怎么更正
在回溯时删除vis标记即可,这样能否找到环?
答案是确定的,因为会遍历到每个路径
#include<bits/stdc++.h> using namespace std; const int maxn=30005; int n,num; int go[maxn][2],fail[maxn]; char s[maxn]; bool end[maxn],vis[maxn]; void extend(int len){ int now=0; for(int i=0;i<len;i++){ int c=s[i]-'0'; if(!go[now][c]) go[now][c]=++num; now=go[now][c]; } end[now]=true; } queue<int> q; void get_fail(){ if(go[0][0]) q.push(go[0][0]); if(go[0][1]) q.push(go[0][1]); while(!q.empty()){ int now=q.front(); q.pop(); for(int i=0;i<=1;i++) if(!go[now][i]) go[now][i]=go[fail[now]][i]; else { fail[go[now][i]]=go[fail[now]][i]; end[go[now][i]]|=end[fail[go[now][i]]]; q.push(go[now][i]); } } } bool dfs(int now){ if(end[now]) return false; for(int i=0;i<=1;i++) if(!vis[go[now][i]]){ vis[go[now][i]]=true; if(dfs(go[now][i])) return true; vis[go[now][i]]=false; } else if(!end[go[now][i]]) return true; return false; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%s",s),extend(strlen(s)); get_fail(); vis[0]=true; printf("%s",dfs(0) ? "TAK" : "NIE"); }