洛谷2444 BZOJ2938 病毒 AC自动机

题目链接
题意:给你n个01串,这些01串不能在你的串中出现,问你是否能构造出无限长的01串。

题解:
乍一看是挺不好想的。无限长会想到得到一个可行的循环节,那些串不能出现却不太好处理。我是听别人讲的这道题,所以我也没有想好该怎么说明才能想到AC自动机的,如果有能给出一个合理的思维过程的朋友欢迎与我交流。
我们考虑AC自动机的原因是它可以用fail指针跳转,如果在跳的过程中形成了环,那么就可以构造无限长的01串了。我们在构造AC自动机时把每个限制不能出现的01串的最后一个字符在trie树上打上不能走的标记,然后根据AC自动机的性质,在处理fail指针的时候我们还要顺便更新这个不能走的标记,也就是如果fail指针将要跳到的点有不能走的标记,那么当前点也应该打上不能走的标记,原因是根据AC自动机的性质,从根到fail指针指向的那个点构成的01串一定是根到现在节点构成的01串的一个后缀,也就是说当前01串的后缀已经是不合法的了,于是就要打上不能走的标记。然后我们用图论的办法找环,从trie树的虚根0号点开始dfs,用类似tarjan的办法,如果能往子节点走,就到子节点dfs,因为如果没有子节点的话在处理fail的同时也处理了它不存在的子节点会跳到哪,这样直到找到一个到达点在当前栈里的点,就找到了环。否则就是没找到环。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,fail[30010],ch[30010][2],book[30010],cnt;
char s[30010];
queue <int> q;
int vis[30010],in[30010];
inline void build()
{
    int cur=0;
    for(int i=1;i<=strlen(s+1);++i)
    {
        if(!ch[cur][s[i]-'0'])
        ch[cur][s[i]-'0']=++cnt;
        cur=ch[cur][s[i]-'0'];
    }
    book[cur]=1;
}
inline void get_fail()
{
    for(int i=0;i<=1;++i)
    {
        if(ch[0][i])
        {
            fail[ch[0][i]]=0;
            q.push(ch[0][i]);
        }
    }
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=0;i<=1;++i)
        {
            if(ch[x][i])
            {
                fail[ch[x][i]]=ch[fail[x]][i];
                q.push(ch[x][i]);
                if(book[fail[ch[x][i]]])
                book[ch[x][i]]=1;
            }
            else
            ch[x][i]=ch[fail[x]][i];
        }
    }
}
inline void dfs(int x)
{
    if(in[x])
    {
        printf("TAK\n");
        exit(0);
    }
    if(vis[x]||book[x])
    return;
    in[x]=1;
    vis[x]=1;
    dfs(ch[x][0]);
    dfs(ch[x][1]);
    in[x]=0;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s+1);
        build();
    }
    get_fail();
    dfs(0);
    printf("NIE\n");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/81396043