[POI2000] 病毒
题目链接(这次是落咕的):戳我
做这个题目之前可以考虑先去把落咕上的两个AC自动机的模板写了——这个 \(\;\)还有这个
AC自动机是什么呢?是一种多模匹配的算法。有可能很多人都说它是KMP+Trie。
一般的AC自动机解决的是字符串匹配一类的问题,但是这道题绕了个弯,让求有没有可能存在一个无限长的串,使得其中不包含给定的一些文本串——也就是说存不存在一个串,无法匹配给定的这些串。
那么也就是说,要尽可能地让模式串失配。有fail指针就要往上面跳。我们在预先处理fail指针的时候就把最后节点的儿子指向了自己fail指针所指的节点的相同的儿子。之后在建出来的trie树上跑dfs,如果处理当前匹配可以跳到一个环里,那么自然可以构成一个无限长的字符串满足题意。反之,则每次往下一层必定有匹配,最终一定会匹配上病毒代码。
所以就是AC自动机+dfs!需要注意的一点是,我们要给病毒串的末尾打标记,然后它的子节点必须也要继承上这个标记(因为如果匹配到这个子节点,那么一定匹配了它的父亲,匹配到它的父亲的时候就已经是匹配上病毒串了)。dfs的时候如果遇到标记直接return,因为这样就匹配上了病毒串,而我们要找的是无法完成匹配的串。还有一点,就是如果遇到非当前次经过的节点(对应代码为pre[now]==1),也要直接return。因为如果遇到了它,那么意味着这之前这个节点并没有成功跳进环,所以进入到这个节点是没用的。
最后注意既然我们是要找当前无法完成匹配(也就是跳进环)的一些节点,那么我们处理完一定要把回溯。。。。。
代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<queue>
#define MAXN 100010
using namespace std;
struct tree{int fail,end,vis[26];}ac[MAXN*10];
int cnt=0,n,t;
int head[MAXN],done[MAXN],pre[MAXN];
string s[2010];
struct Edge{int nxt,to;}edge[MAXN<<1];
inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
inline void build(string s)
{
int len=s.length();
int now=0;
for(int i=0;i<len;i++)
{
if(ac[now].vis[s[i]-'0']==0)
{
ac[now].vis[s[i]-'0']=++cnt;
add(now,ac[now].vis[s[i]-'0']);
}
now=ac[now].vis[s[i]-'0'];
}
ac[now].end=1;
}
inline void get_fail()
{
queue<int>q;
for(int i=0;i<=1;i++)
if(ac[0].vis[i])
ac[ac[0].vis[i]].fail=0,q.push(ac[0].vis[i]);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=0;i<=1;i++)
{
if(ac[u].vis[i]!=0)
{
ac[ac[u].vis[i]].fail=ac[ac[u].fail].vis[i];
q.push(ac[u].vis[i]);
if(ac[ac[u].fail].end==1) ac[u].end=1;
}
else ac[u].vis[i]=ac[ac[u].fail].vis[i];
}
}
}
inline void dfs(int now)
{
if(ac[now].end) return;
if(done[now]){printf("TAK\n");exit(0);}
if(pre[now]) return;
pre[now]=done[now]=1;
for(int i=0;i<=1;i++)
if(ac[now].vis[i])
dfs(ac[now].vis[i]);
done[now]=0;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
scanf("%d",&n);
for(int i=1;i<=n;i++) cin>>s[i],build(s[i]);
get_fail();
dfs(0);
printf("NIE\n");
return 0;
}