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
01
11
00000
样例输出
NIE
AC自动机经典题,题目要求就是要在trie树上在不走到危险节点的情况下,走出一个环。危险节点就是每个病毒字符串的终止节点,如果一个节点的失配标记指向危险节点,那它自己也是危险节点。在建立trie树并找到每个节点的失配节点(但不要忘了标记危险节点)后只要dfs一下AC自动机,每走到一个点打上访问标记,回溯到这个点再把标记删除,如果走到了打标记的点就说明出现了环。
最后附上代码。
#include<cmath> #include<queue> #include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; struct tree { int fail; int vis[3]; bool end; }a[30001]; int cnt; int n; int num; char s[30001]; bool f[30001]; bool v[30001]; void build(char *s) { int l=strlen(s); int now=0; for(int i=0;i<l;i++) { int x=(s[i]-'0'); if(a[now].vis[x]==0) { a[now].vis[x]=++cnt; } now=a[now].vis[x]; } a[now].end=true; } void bfs() { queue<int>q; for(int i=0;i<2;i++) { if(a[0].vis[i]!=0) { a[a[0].vis[i]].fail=0; q.push(a[0].vis[i]); } } while(!q.empty()) { int now=q.front(); q.pop(); for(int i=0;i<2;i++) { if(a[now].vis[i]!=0) { a[a[now].vis[i]].fail=a[a[now].fail].vis[i]; q.push(a[now].vis[i]); if(a[a[a[now].fail].vis[i]].end) { a[a[now].vis[i]].end=true; } } else { a[now].vis[i]=a[a[now].fail].vis[i]; } } } } void dfs(int d) { v[d]=true; for(int i=0;i<2;i++) { if(v[a[d].vis[i]]) { printf("TAK"); exit(0); } else if(!a[a[d].vis[i]].end&&!f[a[d].vis[i]]) { f[a[d].vis[i]]=true; dfs(a[d].vis[i]); } } v[d]=false; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",&s); build(s); } a[0].fail=0; bfs(); dfs(0); printf("NIE"); return 0; }