【洛谷P2444】病毒
题目
题目链接:https://www.luogu.com.cn/problem/P2444
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果 \(\{011, 11, 00000\}\) 为病毒代码段,那么一个可能的无限长安全代码就是 \(010101 \ldots\)。如果 \(\{01, 11, 000000\}\) 为病毒代码段,那么就不存在一个无限长的安全代码。
现在给出所有的病毒代码段,判断是否存在无限长的安全代码。
\(Q\leq 2000;\sum |S|\leq 3\times 10^4\)。
思路
建出 AC 自动机,每一个点向它儿子连边。如果能在不走到中止节点的前提下形成环,那么存在无限长的代码,否则不存在。
注意如果一个点的 \(\text{fail}\) 是终止节点那么这个点也是中指节点。
时间复杂度 \(O(\sum |S|)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=30010;
int n,m,tot,head[N],deg[N];
char s[N];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot; deg[to]++;
}
struct ACA
{
int tot,ch[N][4],fail[N];
bool end[N];
void ins(char *s)
{
int len=strlen(s+1),p=0;
for (int i=1;i<=len;i++)
{
int c=s[i]-48;
if (!ch[p][c]) ch[p][c]=++tot;
p=ch[p][c];
}
end[p]=1;
}
void build()
{
queue<int> q;
if (ch[0][0]) q.push(ch[0][0]);
if (ch[0][1]) q.push(ch[0][1]);
ch[0][2]=ch[0][0]; ch[0][3]=ch[0][1];
while (q.size())
{
int u=q.front(); q.pop();
end[u]|=end[fail[u]];
ch[u][2]=ch[u][0]; ch[u][3]=ch[u][1];
if (!ch[u][2]) ch[u][2]=ch[fail[u]][2];
else fail[ch[u][2]]=ch[fail[u]][2],q.push(ch[u][2]);
if (!ch[u][3]) ch[u][3]=ch[fail[u]][3];
else fail[ch[u][3]]=ch[fail[u]][3],q.push(ch[u][3]);
}
}
void dfs(int x)
{
if (end[x]) return;
if (ch[x][0]) dfs(ch[x][0]);
if (ch[x][1]) dfs(ch[x][1]);
add(x,ch[x][2]); add(x,ch[x][3]);
}
}AC;
void topsort()
{
queue<int> q;
for (int i=0;i<=AC.tot;i++)
if (!deg[i]) q.push(i),m++;
while (q.size())
{
int u=q.front(); q.pop();
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
deg[v]--;
if (!deg[v]) q.push(v),m++;
}
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%s",s+1);
AC.ins(s);
}
AC.build(); AC.dfs(0);
topsort();
if (AC.tot+1==m) cout<<"NIE";
else cout<<"TAK";
return 0;
}