CF-1207 G.Indie Album(Trie上跑AC自动机)

CF-1207 G.Indie Album(Trie上跑AC自动机)

题目链接

题意

n n n个串,串的生成方式有两种:

  1. 单独一个字符

  2. 在上一个串的基础上加一个字符

q q q个询问,问第 i i i个串中出现串 s s s的次数

思路

离线处理询问,把询问的字符串建AC自动机,并得到 f a i l fail fail

对于一个字符串 s s s出现的次数等于 f a i l fail fail书上对应的节点和它所有子节点出现次数的和 ( A C AC AC自动机性质)

T r i e Trie Trie d f s dfs dfs并把对应在 A C AC AC自动机上的节点加1(树状数组),当 d f s dfs dfs走到字符的终点(当前节点带有询问),处理结果.

节点和子树的和可以用树状数组和 d f s dfs dfs序得到

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int n;
char s[maxn];
int ans[maxn];
vector<pair<int,int> > Q[maxn];
struct Bit{
    int c[maxn];
    void init() {fill(c, c+maxn, 0);}
    int lowbit(int x) {return x & (-x);}
    void add(int x, int d) {
        while (x < maxn) {
            c[x] += d;
            x += lowbit(x);
        }
    }
    int getsum(int x) {
        int sum = 0;
        while (x) {
            sum += c[x];
            x -= lowbit(x);
        }
        return sum;
    }
    int query(int l, int r) {
        return getsum(r) - getsum(l-1);
    }
}bit;
struct Trie{
    int nex[maxn][26], idx[maxn];
    int root, p;
    int newnode() {
        fill(nex[p], nex[p]+26, 0);
        return p++;
    }
    void init() {
        p = 0;
        root = newnode();
    }
    void add(int id, int u, int c) {
        u = idx[u];
        if (nex[u][c-'a'] == 0) nex[u][c-'a'] = newnode();
        idx[id] = nex[u][c-'a'];
    }
}trie;
struct AC{
    int nex[maxn][26], fail[maxn];
    int root, p;
    vector<int> g[maxn];
    int in[maxn], out[maxn], cnt;
    int newnode() {
        for (int i = 0; i < 26; ++i) nex[p][i] = -1;
        return p++;
    }
    void init() {
        p = 0;
        cnt = 0;
        root = newnode();
    }
    int insert(char *buf) {
        int now = root;
        for (int i = 0; buf[i]; ++i) {
            if (nex[now][buf[i]-'a'] == -1) {
                nex[now][buf[i]-'a'] = newnode();
            }
            now = nex[now][buf[i]-'a'];
        }
        return now;
    }
    void build() {
        queue<int> que;
        fail[root] = root;
        for (int i = 0; i < 26; ++i) {
            if (nex[root][i] == -1) nex[root][i] = root;
            else {
                fail[nex[root][i]] == root;
                que.push(nex[root][i]);
            }
        }
        while (!que.empty()) {
            int now = que.front();
            que.pop();
            g[fail[now]].push_back(now); // 求fail树
            for (int i = 0; i < 26; ++i) {
                if (nex[now][i] == -1) nex[now][i] = nex[fail[now]][i];
                else {
                    fail[nex[now][i]] = nex[fail[now]][i];
                    que.push(nex[now][i]);
                }
            }
        }
    }
    void dfs(int u) { // 求dfs序
        in[u] = ++cnt;
        for (int i = 0; i < (int)g[u].size(); ++i) {
            int v = g[u][i];
            dfs(v);
        }
        out[u] = cnt;
    }
    void dfs(int u1, int u2) {
        bit.add(in[u2], 1); // 对应AC节点++
        for (int j = 0; j < (int)Q[u1].size(); ++j) { // Trie节点对应有询问
            int pos = Q[u1][j].first;
            int id = Q[u1][j].second;
            ans[id] = bit.query(in[pos], out[pos]);
        }
        for (int i = 0; i < 26; ++i) {
            if (trie.nex[u1][i] == 0) continue;
            dfs(trie.nex[u1][i], nex[u2][i]);
        }
        bit.add(in[u2], -1); // 去掉之前标记
    }
    void solve() {
        build();
        dfs(0); // dfs序
        dfs(0, 0);
    }
}ac; 
int main() {
    scanf("%d", &n);
    trie.init();
    bit.init();
    ac.init();
    for (int i = 1; i <= n; ++i) {
        int op, num;
        char c;
        scanf("%d", &op);
        if (op == 2) {
            scanf("%d %c", &num, &c);
            trie.add(i, num, c);
        }else {
            scanf(" %c", &c);
            trie.add(i, 0, c);
        }
    }
    int q;
    scanf("%d", &q);
    for (int i = 1; i <= q; ++i){
        int id;
        scanf("%d %s", &id, s);
        Q[trie.idx[id]].push_back(make_pair(ac.insert(s), i));
    }
    ac.solve();
    for (int i = 1; i <= q; ++i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值