洛谷2444:病毒
题意:
- 有n个二进制字符串,称为病毒。
- 构造一个二进制字符串,使得没有任何一个病毒出现在这个构造的二进制字符串中。
- 回答是否可以构造这样一个字符串。
思路:
- AC自动机。
- AC自动机是一个多模式匹配的数据结构。
- 我们首先构造(trie)树并构建(fail)指针。
- 这时候(trie)树就不再是(trie)树了,经过加了几个(fail)指针变成了一张有向图。
- 如果存在这样一个无限的字符串,使得没有任何一个病毒是他的子串,那么会有什么情况呢?
- 拿这个字符串到自动机上匹配,无论怎么样也到不了某个病毒串的结尾位置,因为构造的字符串无限长,所以他会在自动机里无限地转圈圈。
- 所以问题转化为:
- 在AC自动机上找一个环,这个环上没有节点是病毒串的结尾。
- 注意一个节点的(fail)节点如果是结尾节点,那么他自身也应该是一个结尾节点。
- 画个图看看
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e4 + 10;
char s[maxn];
int n;
struct AC_Automaton
{
int trie[maxn][5];
int val[maxn];
int fail[maxn];
int tot;
void ins(char *str)
{
int len = strlen(str), p = 0;
for(int k = 0; k < len; k++)
{
int ch = str[k] - '0';
if(trie[p][ch] == 0) trie[p][ch] = ++tot;
p = trie[p][ch];
}
val[p] = 1;
}
void build()
{
queue<int> q;
for(int i = 0; i < 2; i++)
{
if(trie[0][i])
{
//第二层指向根节点
fail[trie[0][i]] = 0;
q.push(trie[0][i]);
}
}
while(q.size())
{
int x = q.front(); q.pop();
for(int i = 0; i < 2; i++)
{
if(trie[x][i])
{
fail[trie[x][i]] = trie[fail[x]][i];
val[trie[x][i]] |= val[fail[trie[x][i]]];
q.push(trie[x][i]);
}
else trie[x][i] = trie[fail[x]][i];
}
}
}
bool v1[maxn];
void dfs(int x)
{
if(v1[x])
{
puts("TAK");
exit(0);
}
if(val[x]) return;
v1[x] = 1;
if(trie[x][0]) dfs(trie[x][0]);
if(trie[x][1]) dfs(trie[x][1]);
v1[x] = 0;
}
}AC;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%s", s);
AC.ins(s);
} AC.build();
AC.dfs(0); //从字典树根节点开始找环
puts("NIE"); //不存在无限的串
return 0;
}