• [POI2000]病毒(补全AC自动机)


    题意

    二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。

    示例:

    例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。

    任务:

    请写一个程序:

    1.在文本文件WIR.IN中读入病毒代码;

    2.判断是否存在一个无限长的安全代码;

    3.将结果输出到文件WIR.OUT中。

    输入格式:

    在文本文件WIR.IN的第一行包括一个整数n(nle 2000)(n2000),表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。

    输出格式:

    在文本文件WIR.OUT的第一行输出一个单词:

    TAK——假如存在这样的代码;

    NIE——如果不存在。

    题解

    很容易想到将病毒串放入Trie树,补全后,如果能找到一个不经过任何串末尾的环就有一个安全代码

    考虑如何找环,本来想的是拓扑排序,不过就算找出一个环,也很难判断是否含末尾点

    然后又不小心听到了dfs,想了想很有道理,于是写出了如下东西

    结果WA了一片,第一个样例就错了.....

    那考虑为什么会这样,按照上面的写法,下个点访问过一定是环吗?

    不一定,补全后就是一个有向图,所以我们拿有向图举例

     

    如果先从1遍历完,然后回到1,再从2到1时,就会判断是环

    然而不是,因为这是有向图,如果是无向图的话确实有环。

    那么考虑怎么更正

    在回溯时删除vis标记即可,这样能否找到环?

    答案是确定的,因为会遍历到每个路径

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn=30005;
    int n,num;
    int go[maxn][2],fail[maxn]; 
    char s[maxn];
    bool end[maxn],vis[maxn];
    
    void extend(int len){
        int now=0;
        for(int i=0;i<len;i++){
            int c=s[i]-'0';
            if(!go[now][c]) go[now][c]=++num;
            now=go[now][c];
        }
        end[now]=true;
    }
    
    queue<int> q;
    
    void get_fail(){
        if(go[0][0]) q.push(go[0][0]);
        if(go[0][1]) q.push(go[0][1]);
        while(!q.empty()){
            int now=q.front();
            q.pop();
            for(int i=0;i<=1;i++)
             if(!go[now][i]) go[now][i]=go[fail[now]][i];
             else {
                 fail[go[now][i]]=go[fail[now]][i];
                 end[go[now][i]]|=end[fail[go[now][i]]];
                 q.push(go[now][i]);
             }
        }
    }
    
    bool dfs(int now){
        if(end[now]) return false;
        for(int i=0;i<=1;i++)
         if(!vis[go[now][i]]){
             vis[go[now][i]]=true;
             if(dfs(go[now][i])) return true;
             vis[go[now][i]]=false;
         }
         else if(!end[go[now][i]]) return true;
        return false;
    }
    
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%s",s),extend(strlen(s));
        get_fail();
        vis[0]=true;
        printf("%s",dfs(0) ? "TAK" : "NIE");
    }
    View Code

     然后在bzoj上就T了,一看评论才知道。

    需要两个bool数组,vis记录是否访问过,访问过就不访问,达到剪枝目的,sta记录是否在当前路径

    不会错吗?考虑如果下一个要遍历的点在我们要的环中,但它访问过了,当第一次访问它时,它就应该找出环,所以不可能。

    luogu真的有点小水

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn=30005;
    int n,num;
    int go[maxn][2],fail[maxn]; 
    char s[maxn];
    bool end[maxn],vis[maxn],sta[maxn];//是否访问过,是否在当前路径 
    
    void extend(int len){
        int now=0;
        for(int i=0;i<len;i++){
            int c=s[i]-'0';
            if(!go[now][c]) go[now][c]=++num;
            now=go[now][c];
        }
        end[now]=true;
    }
    
    queue<int> q;
    
    void get_fail(){
        if(go[0][0]) q.push(go[0][0]);
        if(go[0][1]) q.push(go[0][1]);
        while(!q.empty()){
            int now=q.front();
            q.pop();
            for(int i=0;i<=1;i++)
             if(!go[now][i]) go[now][i]=go[fail[now]][i];
             else {
                 fail[go[now][i]]=go[fail[now]][i];
                 end[go[now][i]]|=end[fail[go[now][i]]];
                 q.push(go[now][i]);
             }
        }
    }
    
    bool dfs(int now){
        if(end[now]) return false;
        for(int i=0;i<=1;i++)
         if(!vis[go[now][i]]){
             sta[go[now][i]]=true;
             vis[go[now][i]]=true;
             if(dfs(go[now][i])) return true;
             sta[go[now][i]]=false;
         }
         else if(sta[go[now][i]]&&!end[go[now][i]]) return true;
        return false;
    }
    
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%s",s),extend(strlen(s));
        get_fail();
        sta[0]=vis[0]=true;
        printf("%s",dfs(0) ? "TAK" : "NIE");
    }
    View Code
  • 相关阅读:
    CURL 命令
    进入容器时给容器root权限
    Nginx性能优化功能- Gzip压缩(大幅度提高页面加载速度)
    Linux防火墙Firewall和Iptables的使用
    Linux 下统计文件的个数
    linux命令: wc命令
    Java static关键字详解
    Jmeter 定时器
    Java数据类型转换:强制类型转换+自动类型转换
    Jmeter中正则表达式提取器使用详解
  • 原文地址:https://www.cnblogs.com/sto324/p/11209785.html
Copyright © 2020-2023  润新知