BZOJ2938病毒——AC自动机+dfs

Description
二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。
示例:
例如如果{011, 11, 00000}为病毒代码段,那么一个可能的无限长安全代码就是010101…。如果{01, 11, 000000}为病毒代码段,那么就不存在一个无限长的安全代码。
任务:
请写一个程序:
l 读入病毒代码;
l 判断是否存在一个无限长的安全代码;
l 将结果输出
Input

第一行包括一个整数n,表示病毒代码段的数目。以下的n行每一行都包括一个非空的01字符串——就是一个病毒代码段。所有病毒代码段的总长度不超过30000。
Output
你应在在文本文件WIN.OUT的第一行输出一个单词:
l TAK——假如存在这样的代码;
l NIE——如果不存在。
Sample Input
3

01

11

00000

Sample Output
NIE


首先在AC自动机上建出所有的01字串。那么关键就在于在AC自动机上如何跑才能验证是否存在。先然我们可以进行DFS,一直往0和1两个方向进行DFS,如果发现往某一边走会走到某一个字串的尾部,就不往那边走。
这样一直DFS,如果能找到我们原来经过的点,那也就形成了一个环,可以无限重复下去,那也就是符合题目要求的循环节。于是输出TAK。
这样理论上就可以了,但是在大数据并且最终答案是NIE的数据时会T。我们可以再考虑当前DFS的性质:每个点只能经过一次,无论其能不能形成一个环。于是我们再开一个数组记录点是否被经过,这个数组的修改与上面找循环节的vis数组几乎相同,只是在回溯的时候不清零。(我的代码中为ask数组)
#include<bits/stdc++.h>
#define MAXN 30005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,cnt=1,flag,nxt[MAXN][2],fail[MAXN],f[MAXN],q[MAXN],vis[MAXN],ask[MAXN];
char s[MAXN];
void build(){
    int pl=1,l=strlen(s+1);
    for(int i=1;i<=l;i++){
        int x=s[i]-'0';
        if(nxt[pl][x]) pl=nxt[pl][x];
        else{
            ++cnt;nxt[pl][x]=cnt;pl=cnt;
        }
    }
    f[pl]=1;
}
void getfail(){
    int h=0,t=0;
    for(int i=0;i<2;i++) if(nxt[1][i]) fail[q[++t]=nxt[1][i]]=1;
    while(h<t){
        int now=q[++h];
        for(int i=0;i<2;i++){
            if(nxt[now][i]){
                fail[q[++t]=nxt[now][i]]=nxt[fail[now]][i];
                f[nxt[now][i]]|=f[nxt[fail[now]][i]];
                if(!fail[nxt[now][i]]) fail[nxt[now][i]]=1;
            }
            else nxt[now][i]=nxt[fail[now]][i];
        }
    }
}
void dfs(int x){
    if(flag) return;
    if(vis[x]){
        flag|=1;return;
    }
    vis[x]=1;ask[x]=1;
    for(int i=0;i<2;i++){
        if(!f[nxt[x][i]]&&(!ask[nxt[x][i]]||vis[nxt[x][i]])) dfs(nxt[x][i]);
        if(flag) return;
    }
    vis[x]=0;
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++){
        scanf("%s",s+1);
        build();
    }
    getfail();
    if(nxt[1][0]) dfs(nxt[1][0]);
    memset(vis,0,sizeof(vis));
    if(nxt[1][1]) dfs(nxt[1][1]);
    puts(flag?"TAK":"NIE");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值