【bzoj2938】[Poi2000]病毒 AC自动机
题目描述
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
l 读入病毒代码;
l 判断是否存在一个无限长的安全代码;
l 将结果输出
输入
第一行包括一个整数n,表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
输出
你应在在文本文件WIN.OUT的第一行输出一个单词:
l TAK——假如存在这样的代码;
l NIE——如果不存在。
样例输入
3
01
11
00000
样例输出
NIE
题解
AC自动机
如果一个串能无限长,那么说明它可以一直匹配,而始终不匹配病毒串。
每次查找到下一个字符时,只有两种可能:
(1)存在子节点,进入该子节点。
(2)不存在子节点,进入fail节点,直至存在子节点。
那么是否能无限匹配,就转化为有没有相应的环。
这里为了方便,将fail合并到子节点。
除了排除病毒串节点以外,还应排除fail指向病毒串的节点,因为该串后缀为病毒串前缀。
然后dfs判环即可。
#include <cstdio> #include <cstring> #include <queue> using namespace std; queue<int> q; int nt[30001][2] , fail[30001] , cnt[30001] , tot = 1; char str[30001]; bool vis[30001] , ins[30001] , flag; int deep[30001] , low[30001] , sta[30001] , ind , h; void build() { int u , t , i; q.push(1); nt[0][0] = nt[0][1] = 1; while(!q.empty()) { u = q.front(); q.pop(); for(i = 0 ; i < 2 ; i ++ ) { if(nt[u][i]) { q.push(nt[u][i]); t = fail[u]; while(t && !nt[t][i]) t = fail[t]; fail[nt[u][i]] = nt[t][i]; cnt[nt[u][i]] |= cnt[nt[t][i]]; } else nt[u][i] = nt[fail[u]][i]; } } } bool dfs(int x) { ins[x] = vis[x] = 1; int i , y; for(i = 0 ; i < 2 ; i ++ ) { y = nt[x][i]; if(ins[y] || (!vis[y] && !cnt[y] && dfs(y))) return 1; } ins[x] = 0; return 0; } int main() { int n , i , t , l; scanf("%d" , &n); while(n -- ) { scanf("%s" , str); l = strlen(str); t = 1; for(i = 0 ; i < l ; i ++ ) { if(!nt[t][str[i] - '0']) nt[t][str[i] - '0'] = ++tot; t = nt[t][str[i] - '0']; } cnt[t] = 1; } build(); printf("%s\n" , dfs(1) ? "TAK" : "NIE"); return 0; }