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;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值