题目链接: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循环是优化的关键,如果最小的病毒字符串被标记了,那么所有包含它的字符串也都该被标记, //所以我们要找到最长匹配后缀串,我们要一直循环,直到找到fail指针跳向的结点的下一位也存在,只有这样才和我们当前结点跳向的下一位一致。 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; }