AC自动机小记

AC自动机

很多人形容ac自动机的思路为trie树上的KMP,不得不说确实很像,唯一的区别在于指针的意义。

fail指针指向一个字串的最长后缀。由它的定义可以得出其一个很简单、很常用的性质:如果trie上的一个节点在目标串中出现了,那么它的fail指针指向的节点也会出现(而且结尾位置和该串一致)。这个性质可以在多模式匹配时用于状态转移(这一点很像kmp)。

trie树中exit数组的思想在这里依然适用,统计答案时很方便。

【ac掉的题目】

P3808 AC自动机模板题,模板题,多模式串、单目标串匹配计数。这个题oi-wiki上有题解,写的听不错的,可以拿来当ac自动机的模板。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ldb;
#define pb push_back

int n;
const int maxn = 1e6 + 5;
char s[maxn], t[maxn];

namespace ac
{
    const int N = 1e6 + 5;
    int trie[N][26], e[N];
    int tot = 0;
    int fail[N];

    void insert(char *r)
    {
        int p = 0;
        for (int i = 0; r[i]; ++i)
        {
            int c = r[i] - 'a';
            if (!trie[p][c])
                trie[p][c] = ++tot;
            p = trie[p][c];
        }
        e[p]++;
    }
    void build()
    { // build ac graph
        queue<int> q;
        for (int i = 0; i < 26; ++i)
            if (trie[0][i])
                q.push(trie[0][i]);
        while (!q.empty())
        {
            int u = q.front();
            q.pop();
            for (int i = 0; i < 26; ++i)
            {
                if (trie[u][i])
                {
                    fail[trie[u][i]] = trie[fail[u]][i];
                    q.push(trie[u][i]);
                }
                else
                    trie[u][i] = trie[fail[u]][i];
            }
        }
    }
    int query(char *r)
    {
        int u = 0, ans = 0;
        for (int i = 0; r[i]; ++i)
        {
            u = trie[u][r[i] - 'a'];
            for (int j = u; j && e[j] != -1; j = fail[j])
            {
                ans += e[j];
                e[j] = -1;
            }
        }
        return ans;
    }
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("ain3", "r", stdin);
#endif
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
    {
        scanf("%s", s);
        ac::insert(s);
    }
    ac::build();
    scanf("%s", t);
    printf("%d\n", ac::query(t));

    return 0;
}

P3796 AC自动机模板加强版 ,稍微不那么板(其实也挺板的),计数题,没有卡时间。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ldb;
#define pb push_back

const int N = 11000;      // number of nodes
const int maxn = 1e6 + 5; // max length of target string

namespace ac
{
    int tot;
    int e[N], tr[N][26];
    int f[N];
    char s[155][75];
    map<int, int> m;
    int ans[155];

    void init()
    {
        tot = 0;
        memset(e, 0, sizeof(e));
        memset(tr, 0, sizeof(tr));
        memset(f, 0, sizeof(f));
        m.clear();
        memset(ans, 0, sizeof(ans));
    }

    void insert(int id)
    {
        scanf("%s", s[id]);
        int u = 0;
        for (int i = 0; s[id][i]; ++i)
        {
            int c = s[id][i] - 'a';
            if (!tr[u][c])
                tr[u][c] = ++tot;
            u = tr[u][c];
        }
        e[u] = 1;
        m[u] = id;
    }

    void build()
    {
        queue<int> q;
        for (int i = 0; i < 26; ++i)
            if (tr[0][i])
                q.push(tr[0][i]);
        while (q.size())
        {
            int u = q.front();
            q.pop();
            for (int i = 0; i < 26; ++i)
            {
                if (tr[u][i])
                    f[tr[u][i]] = tr[f[u]][i], q.push(tr[u][i]);
                else
                    tr[u][i] = tr[f[u]][i];
            }
        }
    }

    void query(char *t, int n)
    {
        int u = 0;
        for (int i = 0; t[i]; ++i)
        {
            u = tr[u][t[i] - 'a'];
            for (int j = u; j; j = f[j])
            {
                if (e[j])
                    ans[m[j]]++;
            }
        }
        int maxv = 0;
        for (int i = 0; i < n; ++i)
            maxv = max(maxv, ans[i]);
        printf("%d\n", maxv);
        for (int i = 0; i < n; ++i)
        {
            if (ans[i] == maxv)
                printf("%s\n", s[i]);
        }
    }

}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("in3796", "r", stdin);
#endif
    int n;
    while (1)
    {
        scanf("%d", &n);
        if (!n)
            return 0;
        char s[maxn];
        ac::init();
        for (int i = 0; i < n; ++i)
            ac::insert(i);
        ac::build();
        scanf("%s", s);
        ac::query(s, n);
    }
}

P3966 单词,要求实现多模式串彼此匹配,思路大概是在trie图上跑反向广搜,根据上文提到的性质不难统计答案。,fail数组用于状态转移、统计答案。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ldb;
#define pb push_back

namespace ac
{
    int tot;
    const int N = 1e6 + 5;
    int e[N], f[N], tr[N][26];
    ll w[N];
    int node[203];
    void insert(char *s, int i)
    {
        int u = 0;
        for (int i = 0; s[i]; ++i)
        {
            int c = s[i] - 'a';
            if (!tr[u][c])
                tr[u][c] = ++tot;
            u = tr[u][c];
            w[u]++;
        }
        node[i] = u;
    }
    stack<int> p;
    void build()
    {
        queue<int> q;
        for (int i = 0; i < 26; ++i)
            if (tr[0][i])
                q.push(tr[0][i]);
        while (q.size())
        {
            int u = q.front();
            q.pop();
            p.push(u);
            for (int i = 0; i < 26; ++i)
            {
                if (tr[u][i])
                {
                    f[tr[u][i]] = tr[f[u]][i];
                    q.push(tr[u][i]);
                }
                else
                    tr[u][i] = tr[f[u]][i];
            }
        }
    }

    void search()
    {
        while (p.size())
        {
            int u = p.top();
            p.pop();
            w[f[u]] += w[u];
        }
    }
    void print(int n)
    {
        for (int i = 0; i < n; ++i)
        {
            printf("%lld\n", w[node[i]]);
        }
    }
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("in", "r", stdin);
#endif
    int n;
    char t[ac::N];
    scanf("%d", &n);
    for (int i = 0; i < n; ++i)
    {
        scanf("%s", t);
        ac::insert(t, i);
    }
    ac::build();
    ac::search();
    ac::print(n);
    return 0;
}

还有一题,Video Game G,这个是ac自动机+DP,估计也是一个挺常见类型,还没A掉。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值