zoukankan      html  css  js  c++  java
  • 【BZOJ2434】[NOI2011]阿狸的打字机

    【BZOJ2434】[NOI2011]阿狸的打字机

    题面

    bzoj

    洛谷

    题解

    我们先想一下最暴力是怎么搞的

    把$AC$自动机建好,每一个节点,从$y$串的结尾节点往上跳它的父亲,

    和普通的$AC$自动机一样跳就好了

    然而这个可以优化一下

    我们将所有询问离线

    每个串统计一次其他串对它的贡献

    就可以有$70pts$了

    $70pts$代码

    #include <iostream> 
    #include <cstdio> 
    #include <cstdlib> 
    #include <cstring> 
    #include <cmath> 
    #include <algorithm> 
    #include <queue> 
    using namespace std;
    const int MAX_N = 2e5 + 5; 
    char s[MAX_N]; 
    int N, tot, nd[MAX_N], ans[MAX_N], sum[MAX_N]; 
    struct Trie { int ch[26], fail, fa, End; } t[MAX_N]; 
    void build() {
        static queue<int> que; 
        for (int i = 0; i < 26; i++) if (t[0].ch[i]) que.push(t[0].ch[i]), t[t[0].ch[i]].fail = 0; 
        while (!que.empty()) { 
            int o = que.front(); que.pop(); 
            for (int i = 0; i < 26; i++) {
                if (t[o].ch[i]) t[t[o].ch[i]].fail = t[t[o].fail].ch[i], que.push(t[o].ch[i]);
                else t[o].ch[i] = t[t[o].fail].ch[i]; 
            } 
        } 
    }
    int query(int y) { 
        int res = 0;
        for (int o = nd[y]; o; o = t[o].fa)
            for (int x = o; x; x = t[x].fail) if (t[x].End) ++sum[t[x].End]; 
        return res; 
    } 
    struct Query { int x, y, id; } q[MAX_N]; 
    bool operator < (const Query l, const Query r) { return l.y < r.y; } 
    
    int main () { 
        scanf("%s", s + 1);
        for (int o = 0, i = 1, l = strlen(s + 1); i <= l; i++) { 
            if ('a' <= s[i] && s[i] <= 'z') {
                if (!t[o].ch[s[i] - 'a']) t[o].ch[s[i] - 'a'] = ++tot, t[tot].fa = o; 
                o = t[o].ch[s[i] - 'a']; 
            } 
            if (s[i] == 'B') o = t[o].fa; 
            if (s[i] == 'P') nd[++N] = o, t[o].End = N; 
        }
        build(); 
        int M; scanf("%d", &M); 
        for (int i = 1; i <= M; i++) scanf("%d", &q[i].x), scanf("%d", &q[i].y), q[i].id = i;
        sort(&q[1], &q[M + 1]); 
        for (int i = 1, j = 1; i <= M; i = j) { 
            query(q[i].y); 
            while (q[j].y == q[i].y) ans[q[j].id] = sum[q[j].x], ++j; 
            fill(&sum[1], &sum[N + 1], 0); 
        } 
        for (int i = 1; i <= M; i++) printf("%d
    ", ans[i]); 
        return 0; 
    } 
    

    然后我们想一下这个过程:

    每一个点往上跳,如果可以跳到一个点,是其他字符串的$End$节点就统计贡献

    所以对于一个点,它跳到它被匹配的模式串的$End$点的次数,就是答案

    换句话说

    对于每个模式串,就是要统计有多少个文本串能跳到它。

    这个就好解决一些了,

    发现对于每个点,它对应的$fail$只有一个

    所以我们将点$i$与$fail_i$连边,

    在新的树上统计:

    每次将串$i$的链上加一,然后将询问离线统计贡献。

    但是这样做还是只有$70pts$,

    因为还是有很多状态被重复统计,

    那么如何继续优化呢?

    接下来的做法就很巧妙了:

    我们保留原来的$trie$和根据$fail$新建的树

    在新树上预处理$dfs$序,

    然后$dfs$遍历一遍原$trie$,入栈时将这个点$+1$

    出栈时$-1$

    如果这个点有结尾的串,就对这个串统计贡献,

    因为我们$dfs$回答询问时只会有那一个串被统计

    所以我们的做法是对的。

    如有什么不理解的地方,可以参见代码

    代码

    #include <iostream> 
    #include <cstdio> 
    #include <cstdlib> 
    #include <cstring> 
    #include <cmath> 
    #include <algorithm> 
    #include <queue>
    #include <vector> 
    using namespace std;
    const int MAX_N = 2e5 + 5; 
    char s[MAX_N]; 
    int N, tot, nd[MAX_N], ans[MAX_N]; 
    struct Trie { int ch[26], cpy[26], fail, fa, End; } t[MAX_N]; 
    void build() {
        static queue<int> que; 
        for (int i = 0; i < 26; i++) if (t[0].ch[i]) que.push(t[0].ch[i]), t[t[0].ch[i]].fail = 0; 
        while (!que.empty()) { 
            int o = que.front(); que.pop(); 
            for (int i = 0; i < 26; i++) {
                if (t[o].ch[i]) t[t[o].ch[i]].fail = t[t[o].fail].ch[i], que.push(t[o].ch[i]);
                else t[o].ch[i] = t[t[o].fail].ch[i]; 
            } 
        } 
    } 
    struct Graph { int to, next; } e[MAX_N << 1]; int fir[MAX_N], e_cnt = 0; 
    void clearGraph() { memset(fir, -1, sizeof(fir)); e_cnt = 0; } 
    void Add_Edge(int u, int v) { e[e_cnt] = (Graph){v, fir[u]}, fir[u] = e_cnt++; } 
    struct Query { int x, id; } ; 
    vector<Query> vec[MAX_N]; 
    int tim, L[MAX_N], R[MAX_N]; 
    void dfs(int x) {
        L[x] = ++tim; 
        for (int i = fir[x]; ~i; i = e[i].next) dfs(e[i].to); 
        R[x] = tim; 
    }
    int c[MAX_N]; 
    inline int lb(int x) { return x & -x; } 
    void add(int x, int v) { while (x <= tim) c[x] += v, x += lb(x); } 
    int sum(int x) { int res = 0; while (x > 0) res += c[x], x -= lb(x); return res; } 
    void DFS(int x) { 
        add(L[x], 1); 
        if (t[x].End) 
            for (vector<Query> :: iterator ite = vec[t[x].End].begin(); ite != vec[t[x].End].end(); ++ite) 
                ans[ite->id] = sum(R[nd[ite->x]]) - sum(L[nd[ite->x]] - 1);
        for (int i = 0; i < 26; i++) 
            if (t[x].cpy[i]) DFS(t[x].cpy[i]); 
        add(L[x], -1); 
    } 
    int main () {
        scanf("%s", s + 1);
        for (int o = 0, i = 1, l = strlen(s + 1); i <= l; i++) { 
            if ('a' <= s[i] && s[i] <= 'z') {
                if (!t[o].ch[s[i] - 'a']) t[o].ch[s[i] - 'a'] = ++tot, t[tot].fa = o; 
                o = t[o].ch[s[i] - 'a']; 
            } 
            if (s[i] == 'B') o = t[o].fa; 
            if (s[i] == 'P') nd[++N] = o, t[o].End = N; 
        } 
        for (int i = 0; i <= tot; i++) 
            for (int o = 0; o < 26; o++) t[i].cpy[o] = t[i].ch[o]; 
        build(); clearGraph(); 
        for (int i = 1; i <= tot; i++) Add_Edge(t[i].fail, i); 
        dfs(0); 
        int M; scanf("%d", &M); 
        for (int i = 1; i <= M; i++) {
            int x, y; scanf("%d%d", &x, &y); 
            vec[y].push_back((Query){x, i}); 
        } 
        DFS(0); 
        for (int i = 1; i <= M; i++) printf("%d
    ", ans[i]); 
        return 0; 
    } 
    
  • 相关阅读:
    树莓派成长日记03
    一些特殊文字的过滤Private Use Area:E000F8FF
    MongoDb 相关
    SQL 相关技术点收集贴
    正则表达式提取文本的日期
    MVC 相关技术点收集贴
    使用 json2.js注意点
    C#画图 GDI+
    PHP模拟POST,验证页面的返回状态
    EF-Entity Framework 相关技术点收集贴
  • 原文地址:https://www.cnblogs.com/heyujun/p/10242941.html
Copyright © 2011-2022 走看看