题目描述
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
l 读入病毒代码;
l 判断是否存在一个无限长的安全代码;
l 将结果输出
输入
第一行包括一个整数n,表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
输出
你应在在第一行输出一个单词:
l TAK——假如存在这样的代码。
l NIE——如果不存在。
样例输入
3
01
11
00000
样例输出
NIE
题解
多模式串匹配问题。
果断AC自动机(因为蒟蒻我也不会别的啊QAQ)
把每个病毒串插进trie树,get_fail这些基本操作大家想必都会啦
本题多了一个奇诡操作:fail环。
首先了解一下fail环
拉过来一张丑陋的图:
如上图为题目描述中为NIE的那个数据(好像少了个0……)。
显然上图中虚线表示的0并不存在。
所以dfs搜到他的时候,这个点指向root的右儿子的那个1的左儿子。
不过遗憾的是
这个0也不存在。
那么只好走这个1的fail指针,即root的右儿子,
然后我们发现,它还是会从root的右儿子去找到当前这个虚线框里的0
这次这个0指向了root的左儿子。
然后再顺下来我们发现又可以找到原来的1啦。
这就是fail环啦(至少我是这么理解的,大神别踩啊QAQ)
然后捏?
我们把插入时的endd设置成危险节点,
即不能访问的节点
然后一遍dfs就出来啦!
代码:
#include<iostream> #include<cstdio> #include<queue> #include<cstring> #include<cmath> #define rint register int using namespace std; char ch[30004]; int n; int trie[30004][2]; int cnt=1,fail[30004]; bool endd[30004],vis[30004]; bool ans=false,failed[30004]; inline void insert(char *str) { int len=strlen(str),p=1; for(rint i=0;i<len;++i) { int l=str[i]-'0'; if(!trie[p][l]) trie[p][l]=++cnt; p=trie[p][l]; } endd[p]=true; } inline void get_fail() { queue <int>q; q.push(1); fail[1]=0; trie[0][0]=trie[0][1]=1; while(!q.empty()) { int l=q.front();q.pop(); for(rint i=0;i<2;++i) { if(trie[l][i]) { fail[trie[l][i]]=trie[fail[l]][i]; if(endd[fail[trie[l][i]]]) endd[trie[l][i]]=true;//注意这两句话,没加毁人生QAQ q.push(trie[l][i]); } else trie[l][i]=trie[fail[l]][i]; } } } inline void dfs(int u) { vis[u]=1; for(int i=0;i<=1;i++) { if(vis[trie[u][i]]) { ans=true; return ; } else if(!failed[trie[u][i]]&&!endd[trie[u][i]]) { failed[trie[u][i]]=1; dfs(trie[u][i]); } } vis[u]=0; return ; } int main() { // freopen("wir213.in","r",stdin); scanf("%d",&n); for(rint i=1;i<=n;++i) { scanf("%s",ch); insert(ch); } get_fail(); // for(rint i=1;i<=cnt;++i)cout<<fail[i]<<endl; dfs(1); if(ans)cout<<"TAK"<<endl; else cout<<"NIE"<<endl; return 0; }
完结撒花~