BZOJ 2434: [Noi2011]阿狸的打字机 ACAM+fail树

title

BZOJ 2434

LUOGU 2414

Description

打字机上只有 \(28\) 个按键,分别印有 \(26\) 个小写英文字母和 B、P 两个字母,是这样工作的:

  • 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
  • 按一下印有 B 的按键,打字机凹槽中最后一个字母会消失。
  • 按一下印有 P 的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

例如,阿狸输入 aPaPBbP ,纸上被打印的字符如下:

a
aa
ab

我们把纸上打印出来的字符串从 \(1\) 开始顺序编号,一直到 \(n\)

打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数 \((x,y)\)(其中 \(1\leqslant x,y\leqslant n\) ),打字机会显示第 \(x\) 个打印的字符串在第 \(y\) 个打印的字符串中出现了多少次。

analysis

比较有意思的 \(ACAM\) 了,不再是套板子的题目。

和一般的 \(ACAM\) 相比,因为多了两个键,所以在建树的时候,就要额外两个操作了:

  1. P :说明已经组成了一个单词,所以记录一下这个单词的结束位置;
  2. B :就往当前节点的父亲走,因为把这个节点删了。

下面就是如何判断单词 \(x\) 是否是 \(y\) 的子串?

说两条性质:

  1. \(Trie\) 树上一个节点的祖先节点所代表的单词,肯定是当前所代表的单词的前缀;

  2. 一个节点的 \(fail\) 指针指向的肯定是当前节点所代表的单词的最长后缀。

然后子串又可以被理解成 前缀的后缀

暴力:从根到 \(y\) 遍历,对于每个节点,向上暴跳 \(fail\) 指针,遇到单词 \(x\)\(+1\)

正解:建立一颗 \(fail\) 树,那么问题就从考虑所有属于 \(y\) 的节点有哪些能够暴跳 \(fail\) 指针到单词 \(x\) 结尾,变成了考虑 \(fail\) 树中 \(y\) 的节点有哪些在 \(x\) 的子树中了。

对于询问呢?在线好像不是很好搞,那就离线。

按照建立 \(Trie\) 的过程遍历一遍 \(Trie\)

每向下走一步,就给刚到的节点的权值 \(+1\) ,每往上跳一步,就给离开的节点的权值 \(-1\)

如果跳到了第 \(i\) 个单词,就处理询问 \(y=i\) 时的所有情况,等同于查询一下 \(fail\) 树中 \(x\) 的子树权值和。

OK。

code

#include<bits/stdc++.h>

const int maxn=1e5+10;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::read;
using IO::write;

struct Graph
{
    int ver[maxn],Next[maxn],head[maxn],len;
    inline void Clear()
    {
        memset(head,0,sizeof(head));
        len=0;
    }

    inline void add(int x,int y)
    {
        ver[++len]=y,Next[len]=head[x],head[x]=len;
    }
}G, A;

namespace BIT
{
    int c[maxn<<1];
    inline int lowbit(int x) { return x & -x; }
    inline void add(int x,int k) { while (x<=maxn) c[x]+=k, x+=lowbit(x); }
    inline int ask(int x) { int ans=0; while (x) ans+=c[x], x-=lowbit(x); return ans; }
    inline int hsh(int l,int r) { return ask(r)-ask(l-1); }
}

using BIT::add;
using BIT::hsh;

int l[maxn],r[maxn],Time,ans[maxn];
struct Aho_Corasick_AutoMaton
{
    int son[maxn][26],fail[maxn],cnt;
    int pos[maxn],fa[maxn];
    inline void insert(char *s)
    {
        int len=strlen(s+1), now=0, id=0;
        for (int i=1; i<=len; ++i)
            if (s[i]=='P') pos[++id]=now;
            else if (s[i]=='B') now=fa[now];
            else
            {
                int c=s[i]-'a';
                if (!son[now][c]) son[now][c]=++cnt, fa[cnt]=now;
                now=son[now][c];
            }
    }

    inline void build()
    {
        std::queue<int>q;
        for (int i=0; i<26; ++i)
            if (son[0][i]) q.push(son[0][i]);
        while (!q.empty())
        {
            int x=q.front();
            q.pop();
            for (int i=0; i<26; ++i)
                if (son[x][i]) fail[son[x][i]]=son[fail[x]][i], q.push(son[x][i]);
                else son[x][i]=son[fail[x]][i];
        }
    }

    inline void solve(char *s)
    {
        int len=strlen(s+1), now=0, id=0;
        add(l[0],1);
        for (int i=1; i<=len; ++i)
            if (s[i]=='P')
            {
                ++id;
                for (int i=A.head[id]; i; i=A.Next[i])
                {
                    int y=pos[A.ver[i]];
                    ans[i]=hsh(l[y],r[y]);
                }
            }
            else if (s[i]=='B') add(l[now],-1), now=fa[now];
            else now=son[now][s[i]-'a'], add(l[now],1);
    }
} ACAM;

inline void dfs(int x)
{
    l[x]=++Time;
    for (int i=G.head[x]; i; i=G.Next[i])
    {
        int y=G.ver[i];
        dfs(y);
    }
    r[x]=Time;
}

char ch[maxn];
int main()
{
    scanf("%s",ch+1);
    ACAM.insert(ch);
    ACAM.build();
    for (int i=1; i<=ACAM.cnt; ++i) G.add(ACAM.fail[i],i);
    int m;read(m);
    for (int i=1,x,y; i<=m; ++i) read(x),read(y),A.Next[i]=A.head[y], A.head[y]=i, A.ver[i]=x;
    dfs(0);
    ACAM.solve(ch);
    for (int i=1; i<=m; ++i) write(ans[i],'\n');
    IO::flush();
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11443519.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值