P2414 [NOI2011] 阿狸的打字机(AC自动机 fail树 dfs序 树状数组)

本文介绍了一种通过Trie树和AC自动机实现的字符串处理算法,用于高效地处理模式匹配查询。作者详细解释了如何构造Trie树,设置fail指针,利用DFS序管理和子树和计数,以及离线处理查询的方法。核心代码展示了如何跟踪单词频率并支持撤销操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

一道比较模板的题,但是比较灵活。

1.先对字符串建立trie树,同时维护每个节点父亲信息fa[now]
P表示改模式串结束
B表示回退到父亲节点

2.对trie树建立fail指针&fail树。

3.对fail树处理出dfs序,以便用树状数组维护子树和。

4.读入询问,按y关键字排序。(离线处理)

5.在trie上遍历字符串,
每到一个单词的结尾(‘P’)统一处理出所有询问。(第x个单词在这个单词中出现了几次)
‘B’:撤销上一步操作
‘*’:树状数组改位置+1,继续向下处理。

我的写法相关变量的逻辑关系更乱,我自己也捋了很久。核心代码处有详细注释
Graph中存fail树:dfn为dfs序;sz为子树大小
注意:将这棵树的dfs序求出之后,原来的trie中的结点序号是无任何意义的,后序操作(单点修改+维护子树sum)都是对于dfs序进行操作。
ACAM中存ac自动机:id为第n个单词与其trie中结尾的映射;fa为父亲节点;ne为fail指针。

#include <bits/stdc++.h>

using namespace std;
//-----pre_def----
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
#define fir(i, a, b) for (int i = (a); i <= (b); i++)
#define rif(i, a, b) for (int i = (a); i >= (b); i--)
#define endl '\n'
#define init_h memset(h, -1, sizeof h), idx = 0;
#define lowbit(x) x &(-x)

//---------------
const int N = 1e5 + 10;
int n, m;
char str[N];
int s[N];
void add(int x, int k)
{
    for (int i = x; i < N; i += lowbit(i))
    {
        s[i] += k;
    }
}
int sum(int x)
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
    {
        res += s[i];
    }
    return res;
}

int sum(int l, int r)
{
    return sum(r) - sum(l - 1);
}

struct Graph
{
    int h[N], e[N], ne[N], dfn[N], sz[N], idx, times;
    Graph()
    {
        init_h;
    }
    void add(int a, int b)
    {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx++;
    }
    void dfs(int u)
    {
        dfn[u] = ++times;//这步操作之后,u无意义了。
        sz[times] = 1;
        for (int i = h[u]; ~i; i = ne[i])
        {
            dfs(e[i]);
            sz[dfn[u]] += sz[dfn[e[i]]];
        }
    }
} G;

struct ACAM
{
    int tr[N][26], id[N], fa[N], idx, n, ne[N];
    void insert()
    {
        int now = 0;
        for (int i = 0; str[i]; i++)
        {
            if (str[i] == 'P') //finish
            {
                id[++n] = now;
            }
            else if (str[i] == 'B')
            {
                now = fa[now]; //back
            }
            else
            {
                int tmp = str[i] - 'a';
                if (!tr[now][tmp])
                    tr[now][tmp] = ++idx, fa[idx] = now;
                now = tr[now][tmp];
            }
        }
    }
    void build() //bfs
    {
        queue<int> q;
        fir(i, 0, 25) if (tr[0][i]) q.push(tr[0][i]);
        while (q.size())
        {
            int t = q.front();
            q.pop();
            fir(i, 0, 25)
            {
                if (tr[t][i])
                {
                    ne[tr[t][i]] = tr[ne[t]][i];
                    q.push(tr[t][i]);
                }
                else
                {
                    tr[t][i] = tr[ne[t]][i];
                }
            }
        }

        fir(i, 1, idx)
        {
            G.add(ne[i], i);
        }
    }

} ac;
vector<PII> q[N];
int ans[N];
void init() {}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    int StartTime = clock();
#endif
    scanf("%s", str);
    ac.insert();
    ac.build();
    scanf("%d", &m);
    fir(i, 1, m)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        q[b].push_back({a, i});
    }
    G.dfs(0);
    add(G.dfn[0], 1);
    int now = 0, nowid = 0;
    for (int i = 0; str[i]; i++)
    {
        if (str[i] == 'P')
        {
            //q[nowid]
            nowid++;
            for (auto item : q[nowid])
            {
                ans[item.second] += sum(G.dfn[ac.id[item.first]], G.dfn[ac.id[item.first]] + G.sz[G.dfn[ac.id[item.first]]] - 1);
            	//item.second 询问编号
            	//item.first 第item.first个单词在第nowid个单词中出现了几次
            	//ac.id[item.first] 第item.first个单词在trie中的结尾点编号
            	//G.dfn[ac.id[item.first]] 将trie中的点编号转化为fail树中的dfs序号
            	//G.sz[G.dfn[ac.id[item.first]]] fail树中该点(dfs序)的子树大小
            }
        }
        else if (str[i] == 'B')
        {
            add(G.dfn[now], -1);
            now = ac.fa[now];
        }
        else
        {
            now = ac.tr[now][str[i] - 'a'];
            add(G.dfn[now], 1);
        }
    }
    fir(i, 1, m) cout << ans[i] << endl;
#ifndef ONLINE_JUDGE
    printf("Run_Time = %d ms\n", clock() - StartTime);
#endif
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值