P2444 [POI2000]病毒 · AC自动机 + dfs

题解

做做笔记 - 我又可以懂ac自动机了orz

已知,
AC自动机是一种多模字符串匹配算法:

构造tire树后,在模式串末尾最后一位的节点上标记,

而平常的AC自动机,是尽量多的接触这些标记

但是,这道题要求是避免的接触这些结束标记

那么从根节点开始,往trie树上跑,能用失配指针跳转就跳转,然后就会发现你陷入了一个死循环,因为如果不是循环,最后结束的位置就一定会有某个病毒的结束标记

且,在处理trie树上的失配指针的时候,因为是bfs式探寻的,失配对象的状态先统计出来,假设其含有结束标记,而后面又有另外路径上节点匹配上它了,那当我们只走过那条路时,也是可以被视作有含有结束标记

比如这样:C是病毒,那么ABCD就不是安全的代码
在这里插入图片描述

所以,现在我们要做的就是找到一个环,环里没有任何结束标记


在这里插入图片描述


#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 10;
int n, m, k;

namespace AC {//AC自动机板子
    const int maxn = 2;

    struct Tree {//字典树
        int fail;//失配指针
        int vis[maxn];//子节点位置
        int end;//标记有几个单词以这个节点
    } AC[N];//trie树 根节点为0

    int cnt = 0;//编号


    void clear(int x) {//清空
        memset(AC[x].vis, 0, sizeof(AC[x].vis));
        AC[x].fail = 0;//结束标志 一般以0为root
        AC[x].end = 0;
    }

    void build(string s) {
        int len = s.length();
        int now = 0;//字典树当前指针
        for (int i = 0; i < len; ++i) {
            if (AC[now].vis[s[i] - '0'] == 0) {
                //trie树没有这个子节点
                AC[now].vis[s[i] - '0'] = ++cnt;
                clear(cnt);
            }
            now = AC[now].vis[s[i] - '0'];
        }
        AC[now].end = 1;//标记单词结尾
    }

    queue<int> q;

    void get_fail() {//构造fail指针
        for (int i = 0; i < maxn; ++i) {//第二层提前预处理
            if (AC[0].vis[i]) {
                AC[AC[0].vis[i]].fail = 0;//指向根节点
                q.push(AC[0].vis[i]);
            }
        }

        while (!q.empty()) {
            int u = q.front();
            q.pop();

            for (int i = 0; i < maxn; ++i) {
                if (AC[u].vis[i]) {//存在子节点
                    AC[AC[u].vis[i]].fail = AC[AC[u].fail].vis[i];
                    //子节点的fail指针 指向父节点fail指针所指向的节点 的相同的子节点 vis[i]相同
                    //如果那个子节点不存在 相当于该子节点fail=0 指向了根节点
                    AC[AC[u].vis[i]].end |= AC[AC[AC[u].fail].vis[i]].end;
                    q.push(AC[u].vis[i]);
                } else {
                    //不存在子节点
                    AC[u].vis[i] = AC[AC[u].fail].vis[i];
                    //当前节点的子节点 指向 当前节点fail指针指向的节点的子节点
                    //把当前节点fail指向的节点 的子节点 作为自己的子节点
                }

            }
        }
    }

    int AC_Query(string s) {//这里的s是被匹配的文本串
        int len = s.length();
        int now = 0, ans = 0;
        for (int i = 0; i < len; ++i) {
            now = AC[now].vis[s[i] - '0'];
            for (int j = now; j && AC[j].end != -1; j = AC[j].fail) {
                //j不能延伸到根节点 且AC[j]不能是走过的
                ans += AC[j].end;
                AC[j].end = -1;
            }
        }
        return ans;
    }

    void init() {
        cnt = 0;
        clear(0);
    }
}
using namespace AC;

bool v1[N];//判断是否是当前搜索路径上的一点
bool v2[N];//判断是历史路径上搜过的点

void dfs(int x) {
    if (v1[x]) {//环
        puts("TAK");
        exit(0);
    }

    //如果又重回到曾经走不通的路径里 或者当前路径有结束标记
    if (v2[x] || AC::AC[x].end) return;

    v1[x] = 1;
    v2[x] = 1;

    dfs(AC::AC[x].vis[0]);
    dfs(AC::AC[x].vis[1]);
    
    v1[x] = 0;
}

string s;

int main() {
    ios::sync_with_stdio(0);

    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> s;
        build(s);
    }
    get_fail();
    dfs(0);
    puts("NIE");

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值