P2414 [NOI2011] 阿狸的打字机(AC自动机,fail树)

Posted H-w-H

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2414 [NOI2011] 阿狸的打字机(AC自动机,fail树)相关的知识,希望对你有一定的参考价值。

P2414[NOI2011] 阿狸的打字机

题意:

在一串长字符串中,可以构建出多个字符串,n组询问,问第x个字符串在第y个字符串中出现的次数。

思路:

先把每个字符串插入到trie树上(注意插入的方法)

再对每个询问按照y进行归类,相同的y则放到同一个数组中,方便得到答案。

再求出每个节点的fail,建出一棵fail树,用dfs找出fail树的dfs序,之后就能用树状数组维护dfs序来求子树权值和。

最后就是求答案,枚举整个字符串,遇到小写字母就在trie上跳到下一个节点再对它的dfs编号在树状数组上加一,遇到B就对它的dfs编号在树状数组上加一再跳到它的父亲节点,遇到P就直接统计在当前到达的第几个字符串中,第x这个字符串的trie编号对应的dfs编号的子树权值大小就是答案。

解释一些地方

trie的插入方法,因为我们对打印出来的串进行重新插入的话会有很多重复的跳转,但我们能用原字符串的B,P直接在trie树上跳转和新建节点。

建fail树是用来干嘛的呢?

我们把每个节点指向的fail数组当成父节点,每个节点有且仅有一个父节点,可以证明可以构建一棵fail树。

根据fail数组的定义,fail数组里面存放的是当以当前节点为后缀的字符串 在 模式串中能找到最长的相同前缀的编号

所以对于fail树的一个节点说,当前节点的儿子们所代表的字符串都会有一个后缀等于当前节点所代表的字符串。

对于一棵子树来说,dfs序时连续的,所以我们用树状数组来维护这一段连续的dfs序,就能的到子树权值。

代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const ll N = 1e6 + 10;
const ll M = 1e6 + 10;

int trie[N][30], tot, fail[N], fa[N];
int idx[N];
int head[N], nex[N], to[N], e = 0;
int tr[N];
int df[N], sz[N], num;
void add(int x, int y) {
    while(x <= num) {
        tr[x] += y;
        x += (x & (-x));
    }
}
int query(int x) {
    int ans = 0;
    while(x) {
        ans += tr[x];
        x -= (x & (-x));
    }
    return ans;
}

void addE(int x, int y) {
    to[++e] = y;
    nex[e] = head[x];
    head[x] = e;
}



void bfs() {
    queue<int> q;
    for(int i=0; i<26; i++) {
        if(trie[0][i]) q.push(trie[0][i]); 
    }
    while(q.size()) {
        int p = q.front();
        q.pop();
        for(int i=0; i<26; i++) {
            if(trie[p][i]) fail[trie[p][i]] = trie[fail[p]][i], q.push(trie[p][i]);
            else trie[p][i] = trie[fail[p]][i];
        }
    }
    for(int i=1; i<=tot; i++) {
        addE(fail[i], i);
        // cout << fail[i] << ' ' << i << endl;
    }
}

void dfs(int x) {
    df[x] = ++num, sz[x] = 1;
    for(int i=head[x]; i; i=nex[i]) {
        dfs(to[i]);
        sz[x] += sz[to[i]];
    }
}

struct Y {
    int id, a;
};
vector<Y> q[N];
string s, x;
int ans[N];
int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    std::ios::sync_with_stdio(false);
    int aa = 0, p = 0;
    cin >> s;
    int len = s.size();
    for(int i=0; i<len; i++) {
        if(s[i] == 'P') idx[++aa] = p;
        else if(s[i] == 'B') p = fa[p];
        else {
            int u = s[i] - 'a';
            if(!trie[p][u]) trie[p][u] = ++tot, fa[tot] = p;
            p = trie[p][u];
        }
    }
    bfs();
    dfs(0);
    int n, cnt = 1;
    cin >> n;
    for(int i=1; i<=n; i++) {
        int x, y;
        cin >> x >> y;
        q[y].push_back({i, x});
    }
    // for(int i=0; i<=tot; i++) cout << df[fail[i]] << ' ' << df[i] << endl;
    p = 0;
    for(int i=0; i<len; i++) {
        if(s[i] == 'P') {
            for(auto it : q[cnt]) {
                ans[it.id] = query(df[idx[it.a]]+sz[idx[it.a]]-1) - query(df[idx[it.a]]-1);
            }
            cnt++;
        }
        else {
            if(s[i] != 'B') {
                p = trie[p][s[i]-'a'];
                add(df[p], 1);
            }
            else {
                add(df[p], -1);
                p = fa[p];
            }
        }
    }
    for(int i=1; i<=n; i++) {
        cout << ans[i] << endl;
    }
}

以上是关于P2414 [NOI2011] 阿狸的打字机(AC自动机,fail树)的主要内容,如果未能解决你的问题,请参考以下文章

[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组

2434: [Noi2011]阿狸的打字机

AC日记——[Noi2011]阿狸的打字机 bzoj 2434

BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

bzoj 2434 [Noi2011]阿狸的打字机——AC自动机

bzoj2434: [Noi2011]阿狸的打字机 字符串-AC自动机-BIT