题目链接:https://www.luogu.org/problem/P2444
题意:给你一些字符串,问能不能找到一个无限长的字符串,使得给定的这些字符串不会出现在该无限长字符串中
一般我们写ac自动机都是尽可能的使多匹配,而本题反其道而行,要尽可能的不匹配,那么我们可以遇到fail标记就跳(因为一个字符串的标记是在最后,中途就调走了肯定就不会遇到了)。如果存在一个无限长的字符串,那么我们内部肯定会形成一个环,且这个环中不会有带结束标记的点,且这个环一定要包含根节点。
PS:这题数据量非常小,我自己想过数据量大了可以结合拓扑排序,但入度不是指向fail边的起点,而是终点,这样可以从小的更新大的,但最后拓扑排序fake了,我做不出来,只能用题解方法了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=30007;
const int inf=0x3f3f3f3f;
const int N=1e7;
const ll mod=998244353;
#define meminf(a) memset(a,0x3f,sizeof(a))
#define mem0(a) memset(a,0,sizeof(a))
char a[maxn];
struct node{
int end;
int vis[2];
int fail;
}ac[maxn];
int cnt=0;
bool v[maxn],w[maxn];
//分别表示结点i是否在当前路径当中,以及结点i之前是否被访问过
void insert(char * s){
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++){
if(ac[now].vis[s[i]-48]==0) ac[now].vis[s[i]-48]=++cnt;
now=ac[now].vis[s[i]-48];
}
ac[now].end=1;
}
void get_fail(){
queue<int> que;
if(ac[0].vis[0]!=0)que.push(ac[0].vis[0]);
if(ac[0].vis[1]!=0)que.push(ac[0].vis[1]);
while(!que.empty()){
int u=que.front();que.pop();
for(int i=0;i<=1;i++){
if(ac[u].vis[i]!=0){
int temp=ac[u].fail;
que.push(ac[u].vis[i]);
while(temp>0&&ac[temp].vis[i]==0) temp=ac[temp].fail;
//这个while循环是优化的关键,如果最小的病毒字符串被标记了,那么所有包含它的字符串也都该被标记,
//且还要保证是完整的包含
ac[ac[u].vis[i]].fail=ac[temp].vis[i];
if(ac[ac[temp].vis[i]].end) ac[ac[u].vis[i]].end=1;
}
else ac[u].vis[i]=ac[ac[u].fail].vis[i];
}
}
}
void dfs(int d){
v[d]=true;
for(int i=0;i<=1;i++){
if(v[ac[d].vis[i]]){
//此时已经找到环了
printf("TAK\n");
exit(0);
}else if(!ac[ac[d].vis[i]].end&&!w[ac[d].vis[i]]){
w[ac[d].vis[i]]=true;
dfs(ac[d].vis[i]);
}
}
v[d]=false;
}
int main(){
int n;scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s",a);
insert(a);
}
get_fail();
dfs(0);
printf("NIE\n");
return 0;
}