首页 > 其他 > 详细

[NOI2011]阿狸的打字机

时间:2020-03-13 13:46:28      阅读:64      评论:0      收藏:0      [点我收藏+]

题意:Luogu
题解:AC自动机
对于fail树,我们有性质:如果有一条边\((u,v)\),则\(u\)代表的字符串必然为\(v\)后缀
对于trie树,我们有性质:如果有一条边\((u,v)\),则\(u\)代表的字符串必然为\(v\)前缀
还有一个性质:字串必定是某一后缀的一个前缀


询问是这样的:对于询问\((x,y)\),求第\(x\)个打印的字符串在第\(y\)个打印的字符串中出现了多少次。
考虑对于每一组询问\((x,y)\),我们可以先把询问离线,按y排序
我们还要预处理出每一个串的结束节点\(root\)和这个节点对应的编号\(id\)
按trie树建树的顺序dfs,每递归到一个点,如果这是一个结束节点,就处理掉这个点的所有询问


我们首先把fail树的dfs序求出来,设为\(dfs\_l\)\(dfs\_r\)
在trie树上dfs时,先对这个点的\(dfs\_l[rt]\)的值+1
对于每个询问\(x\),答案即为\(\big[dfs\_l[root[q[i].x]],dfs\_r[root[q[i].x]\big]\)的区间和
dfs回溯时再-1
所以上树状数组即可


问题来了,为什么可以这样?
下面是一个例子:插入的是hers his she i
这是连了fail指针的trie树(连向根节点的fail指针没放上去)
技术分享图片
这是fail树
技术分享图片
如果我们要查 i 在 his 中的出现次数
我们dfs到了6号节点,\(val[6]+=1\)
这是一个结束节点,所以我们开始处理询问
查询,查\([9,10]\)区间和(也就是10号节点的子树)
答案是1(因为此时trie树上4,5,6节点值都为1,对应在fail树上就是8,6,10节点为1)


统计区间保证了我们要找的模式串(i)是它子树中的所有串的后缀
trie树的dfs方式保证了我们统计的答案是所有原串(his)的前缀(h,hi,his)
两者的交就是所有原串的前缀的后缀是模式串的数量,也就是所求答案
最后上代码,大多数都封了namespace,应该能看懂吧

#include<bits/stdc++.h>
using namespace std;
inline void read(int& x)
{
    x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 100005
int root[maxn], id[maxn], ql[maxn], qr[maxn], ans[maxn];
struct Query
{
    int x, y, id, ans;
    bool operator < (const Query& p) const { return y < p.y; }
}q[maxn];
namespace Tree
{
    struct Edge
    {
        int fr, to;
    }eg[maxn << 1];
    int head[maxn], edgenum;
    inline void add(int fr, int to)
    {
        eg[++edgenum] = { head[fr],to };
        head[fr] = edgenum;
    }
    int dfn_clock, dfs_l[maxn], dfs_r[maxn];
    void dfs(int rt)
    {
        dfs_l[rt] = ++dfn_clock;
        for (int i = head[rt]; i; i = eg[i].fr) dfs(eg[i].to);
        dfs_r[rt] = dfn_clock;
    }
};
using namespace Tree;
namespace Bitarray
{
    int c[maxn];
    inline int lowbit(int& x) { return x & (-x); };
    inline void update(int& pos, int v) { for (int i = pos; i <= dfn_clock; i += lowbit(i)) c[i] += v; }
    inline int query(int pos) { int ans = 0; for (int i = pos; i; i -= lowbit(i)) ans += c[i]; return ans; }
}
using namespace Bitarray;
namespace AC_automaton
{
    int son[maxn][26], fail[maxn], fa[maxn], tot, Son[maxn][26];
    //有两个son是因为第一次会把原来的trie树补成图(也就是加了fail),然而第二次我们需要dfs的是树
    inline void getfail()//我把建fail树封到这里面了
    {
        queue<int> q;
        for (int i = 0; i < 26; ++i)
            if (son[0][i]) q.push(son[0][i]), add(0, son[0][i]);
        while (!q.empty())
        {
            int tp = q.front(); q.pop();
            for (int i = 0; i < 26; ++i)
            {
                if (son[tp][i])
                {
                    fail[son[tp][i]] = son[fail[tp]][i];
                    q.push(son[tp][i]);
                    add(fail[son[tp][i]], son[tp][i]);
                }
                else son[tp][i] = son[fail[tp]][i];
            }
        }
    }
};
using namespace AC_automaton;
char s[maxn];
int cnt, Q;
void Dfs(int rt)//得到答案
{
    update(dfs_l[rt], 1);
    if (id[rt])
        for (int i = ql[id[rt]]; i <= qr[id[rt]]; ++i)
            q[i].ans = query(dfs_r[root[q[i].x]]) - query(dfs_l[root[q[i].x]] - 1);
    for (int i = 0; i < 26; ++i) if (Son[rt][i]) Dfs(son[rt][i]);
    update(dfs_l[rt], -1);
}
int main()
{
    scanf("%s", s);
    for (int i = 0, rt = 0; s[i]; ++i)
    {
        if (s[i] >= 'a' && s[i] <= 'z')
        {
            if (!son[rt][s[i] - 'a']) son[rt][s[i] - 'a'] = ++tot, fa[tot] = rt;
            rt = son[rt][s[i] - 'a'];
        }
        else if (s[i] == 'B') rt = fa[rt];
        else root[++cnt] = rt, id[rt] = cnt;
    }
    for (int i = 0; i <= tot; ++i)
        for (int j = 0; j < 26; ++j) Son[i][j] = son[i][j];
    getfail(); dfs(0);
    read(Q);
    for (int i = 1; i <= Q; ++i) read(q[i].x), read(q[i].y), q[i].id = i;
    sort(q + 1, q + Q + 1);
    for (int i = 1, pos = 1; i <= Q; i = pos)//合并询问
    {
        ql[q[i].y] = i;
        while (q[pos].y == q[i].y) ++pos;
        qr[q[i].y] = pos - 1;
    }
    Dfs(0);
    for (int i = 1; i <= Q; ++i) ans[q[i].id] = q[i].ans;
    for (int i = 1; i <= Q; ++i) printf("%d\n", ans[i]);
    return 0;
}

[NOI2011]阿狸的打字机

原文:https://www.cnblogs.com/123789456ye/p/12439141.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!