zoukankan      html  css  js  c++  java
  • 阿狸的打字机(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
    ", ans[i]);
        }
    
        return 0;
    }
    /**
    aPaPBbP
    3
    1 2
    1 3
    2 3
    */
    
  • 相关阅读:
    jquery 一键复制文本到剪切板
    C#根据当前时间获取周,月,季度,年度等时间段的起止时间
    Asp.Net : Page.RegisterStartupScript及 不执行的原因
    mysql 查询当天、本周,本月,上一个月的数据
    ASP.NET: Cookie会话丢失,Session超时配置
    SQL :“传入的表格格式数据流(TDS)远程过程调用(RPC)协议流不正确” 错误
    JS:Math 对象方法
    ASP.NET上传文件到远程服务器(HttpWebRequest)
    Javascript 使用postMessage对iframe跨域传值或通信
    C#中out和ref之间的区别
  • 原文地址:https://www.cnblogs.com/Q1143316492/p/9576583.html
Copyright © 2011-2022 走看看