阿狸的打字机(AC自动机+fail树应用+dfs序+树状数组)

目录

阿狸的打字机(AC自动机经典题)

标签(空格分隔): ac自动机 fail树

概述:

写完这道题感觉整个人都升华了。。。断断续续写了三天。

题面:

题目描述 Description

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机 上只有 28 个按键,分别印有 26 个小写英文字母和'B'、'P'两个字母。 经阿狸研究发现,这个打字机是这样工作的:

 输入小写字母,打字机的一个凹槽中会加入这个字母(按 P 前凹槽中至 少有一个字母)。

 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。

 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并 换行,但凹槽中的字母不会消失(保证凹槽中至少有一个字母)。

例如,阿狸输入 aPaPBbP,纸上被打印的字符如下: a aa ab 我们把纸上打印出来的字符串从 1 开始顺序编号,一直到 n。打字机有一个 非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数 (x,y)(其中 1≤x,y≤n),打字机会显示第 x 个打印的字符串在第 y 个打印的字符串 中出现了多少次。 阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助 他么?

输入描述 Input Description

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。 第二行包含一个整数 m,表示询问个数。 接下来 m 行描述所有由小键盘输入的询问。其中第 i 行包含两个整数 x, y, 表示第 i 个询问为(x, y)。

输出描述 Output Description

输出 m 行,其中第 i 行包含一个整数,表示第 i 个询问的答案。

样例输入 Sample Input
aPaPBbP

3

1 2

1 3

2 3

样例输出 Sample Output
2

1

0

数据范围及提示 Data Size & Hint

\(1≤n≤ 1e5,1≤m≤ 1e5\)

详解:

题外话:2018年牛客多校的一题ac自动机,知道了补全trie图和普通的暴力会跳的fail指针的写法差别。两种都是对的,那一题有退格操作,每次退格都多一个字符串。暴力回跳的会超时。ac自动机的fail指针的求法有两种。

其一是在暴力回跳的版本。

    inline void Build()
    {
        queue<int>que;
        tree[0].fail = -1;
        que.push(0);
        while(!que.empty()) {
            int now = que.front();
            que.pop();
            for(int i = 0; i < 26; i ++ ) {
                if(tree[now].childs[i]) {
                    if(now == 0) {
                        tree[ tree[now].childs[i] ].fail = 0;
                    } else {
                        int v = tree[now].fail;
                        while(v != -1) {
                            if(tree[v].childs[i]) {
                                tree[ tree[now].childs[i] ].fail = tree[v].childs[i];
                                break;
                            }
                            v = tree[v].fail;
                        }
                        if(v == -1) {
                            tree[ tree[now].childs[i] ].fail == 0;
                        }
                    }
                    que.push(tree[now].childs[i]);
                }
            }
        }
    }

其二是本题写的版本

    void get_fail() {
        queue<int>que;
        fail[root] = root;
        for(int i = 0; i < 26; i++ ) {
            if(ch[root][i] == -1) {
                ch[root][i] = root;
            } else {
                fail[ ch[root][i] ] = root;
                que.push(ch[root][i]);
            }
        }
        while(!que.empty()) {
            int now = que.front();
            que.pop();
            for(int i = 0; i < 26; i++ ) {
                if(ch[now][i] == -1) {
                    ch[now][i] = ch[ fail[now] ][i];
                } else {
                    fail[ ch[now][i] ] = ch[ fail[now] ][i];
                    que.push(ch[now][i]);
                }
            }
        }
    }

优劣也很明显,第二种避免了不断回跳造成的时间消耗。那题多校卡的就是这个。但是却破坏了字典树原有的结构。对于这题没有影响。

为了练习所以我写的是补全trie图的版本的自动机。

回归本题:

我们有一个暴力的思路。y结尾的字符串的fail指针指向x。那么根到x的字符串是,根到y结尾的字符串的后缀(fail指针的意义所在)。

那么我们对于每个答案,只要枚举根到y的路径上的每个结点。的fail指针如果经过若干次跳跃到了x。那么答案加加。这样显然太暴力。

我们可以反向思维。

fail指针反向建树。我们在root 到 y的路径全部加一。对于x。我们算子树中有多少个1即可。

维护子树我们需要dfs序 + 单点更新,区间求和的数据结构。这里用的是树状数组

代码:

#include <bits/stdc++.h>

using namespace std;
const int MAXN = 1e6 + 7;

struct BIT {

    static const int MAXN = 1e6 + 7;

    int c[MAXN];

    inline void init() {
        memset(c, 0, sizeof(c));
    }

    inline int lowbit(int x) {
        return x & -x;
    }

    inline void add(int pos, int val) {
        for(int i = pos; i < MAXN; i += lowbit(i) ) {
            c[i] += val;
        }
    }

    inline int sum(int pos) {
        int ans = 0;
        for(int i = pos; i > 0; i -= lowbit(i)) {
            ans += c[i];
        }
        return ans;
    }

} bit;

char buf[MAXN];

int lef[MAXN], rig[MAXN], dfs_index, ans[MAXN], vis[MAXN];

vector<pair<int, int> > query[MAXN]; /// q[y] = <x, id>

struct Node {
    int to, w, next;
} edge[MAXN * 4];

int first[MAXN], sign;

void init() {
    memset(first, -1, sizeof(first));
    sign = 0;
}

void add_edge(int u, int v, int w) {
    edge[sign].to = v;
    edge[sign].w = w;
    edge[sign].next = first[u];
    first[u] = sign++;
}

int pos[MAXN];

struct ACauto {

    static const int MAXN = 1e6 + 7;

    int fail[MAXN], ch[MAXN][30], cnt[MAXN], tot, root, fa[MAXN];

    int new_node() {
        memset(ch[tot], -1, sizeof(ch[tot]));
        fail[tot] = -1;
        return tot++;
    }

    void init() {
        tot = 0;
        root = new_node();
    }

    void insert(char *str) {
        int now = root, indexs = 0;
        for(int i = 0; str[i]; i++ ) {
            if(str[i] == 'P') {
                pos[++indexs] = now;
            } else if(str[i] == 'B') {
                now = fa[now];
            } else {
                if(ch[now][ str[i] - 'a' ] == -1) {
                    ch[now][ str[i] - 'a' ] = new_node();
                    fa[ ch[now][ str[i] - 'a' ] ] = now;
                }
                now = ch[now][ str[i] - 'a' ];
            }
        }
    }

    void get_fail() {
        queue<int>que;
        fail[root] = root;
        for(int i = 0; i < 26; i++ ) {
            if(ch[root][i] == -1) {
                ch[root][i] = root;
            } else {
                fail[ ch[root][i] ] = root;
                que.push(ch[root][i]);
            }
        }
        while(!que.empty()) {
            int now = que.front();
            que.pop();
            for(int i = 0; i < 26; i++ ) {
                if(ch[now][i] == -1) {
                    ch[now][i] = ch[ fail[now] ][i];
                } else {
                    fail[ ch[now][i] ] = ch[ fail[now] ][i];
                    que.push(ch[now][i]);
                }
            }
        }

        for(int i = 1; i < tot; i++ ) {
            add_edge(fail[i], i, 1);
        }
    }

    void cal(char *str) {
        int now = root, id = 0;
        bit.add(lef[0], 1);
        for(int i = 0; str[i]; i++ ) {
            if(str[i] == 'P') {
                id++;
                for(int j = 0; j < query[id].size(); j++ ) {
                    int x = pos[query[id][j].first];
                    ans[ query[id][j].second ] = bit.sum(rig[x]) - bit.sum(lef[x] - 1);
                }
            } else if(str[i] == 'B') {
                bit.add(lef[now], -1);
                now = fa[now];
            } else {
                now = ch[now][ str[i] - 'a' ];
                bit.add(lef[now], 1);
            }
        }
    }

} ac;

void dfs1(int x) {
    lef[x] = ++dfs_index;
    for(int i = first[x]; ~i; i = edge[i].next) {
        dfs1(edge[i].to);
    }
    rig[x] = dfs_index;
}

int main() {

    bit.init();
    ac.init();
    init();

    scanf("%s", buf);
    ac.insert(buf);
    ac.get_fail();

    dfs1(0);

    int q;
    scanf("%d", &q);
    for(int i = 1; i <= q; i++ ) {
        int x, y;
        scanf("%d %d", &x, &y);
        query[y].push_back(make_pair(x, i));
    }
    ac.cal(buf);
    for(int i = 1; i <= q; i++ ) {
        printf("%d\n", ans[i]);
    }

    return 0;
}
/**
aPaPBbP
3
1 2
1 3
2 3
*/

猜你喜欢

转载自www.cnblogs.com/Q1143316492/p/9576583.html