【BZOJ-2434】阿狸的打字机 AC自动机 + Fail树 + DFS序 + 树状数组

2434: [Noi2011]阿狸的打字机

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 2022  Solved: 1158
[Submit][Status][Discuss]

Description

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。
经阿狸研究发现,这个打字机是这样工作的:
l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。
l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。
l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。
例如,阿狸输入aPaPBbP,纸上被打印的字符如下:
a
aa
ab
我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

Input

输入的第一行包含一个字符串,按阿狸的输入顺序给出所有阿狸输入的字符。
第二行包含一个整数m,表示询问个数。
接下来m行描述所有由小键盘输入的询问。其中第i行包含两个整数x, y,表示第i个询问为(x, y)。

Output

 输出m行,其中第i行包含一个整数,表示第i个询问的答案。

Sample Input

aPaPBbP
3
1 2
1 3
2 3

Sample Output

2
1
0

HINT

1<=N<=10^5

1<=M<=10^5
输入总长<=10^5

Source

Trie

Solution

好题,值得多次练习

AC自动机无疑,建出Fail指针的同时,根据Fail指针反向建树,即Fail树

以x为根的子树沿着fail指针倒着走能找到多少个y路径上的点就说明x在y上出现过几次。

那么利用DFS,求出每个点的时间戳(即每个节点最先到达的时间和最后到达时间),那么发现,对于子树,是一种类似前缀和的存在

那么用线段树或者树状数组来维护一下

值得注意的几点:(皆为亲身经历)

首先在建Trie树的过程中,应该考虑直接对整个操作串进行处理,不断分离去插入会TLE

对于操作串的长度,不应该直接在for循环中使用strlen(),应该先记录下来len=strlen()后循环中直接用len,否则更TLE

不要企图在线处理,应该优先考虑离线处理,否则也会出现奇怪的错误

对于Fail树,应该建单向边,并非双向边,否则可能出现错误

数组要开的够大,题目中10^5,开到10^5+100,Tyvj秒A,CodeVS秒A,BZOJ狂WA不止,怒开2×10^5秒A...不知所措

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
int read()
{
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
#define N 210000
int son[N][30],fail[N],q[N],sz,t,len,m,ans[N],fa[N],loc[N];
char s[N],opt[N];
struct data{int to,next;}edge[N],ask[N];
int cn,cnt,head[N],heada[N],lastq[N];
void addedge(int u,int v)
{cnt++;edge[cnt].to=v;edge[cnt].next=head[u];head[u]=cnt;}
void addask(int x,int y,int z)
{ask[z].next=heada[y];heada[y]=z;ask[z].to=x;}
int tree[N];
inline int lowbit(int x){return x&(-x);}
void change(int loc,int val)
{
    for (int i=loc; i<=t; i+=lowbit(i))
        tree[i]+=val;
}
int query(int loc)
{
    int ans=0;
    for (int i=loc; i>=1; i-=lowbit(i))
        ans+=tree[i];    
    return ans;
}
int L[N],R[N];
void dfs(int x)
{
    L[x]=++t;
    for (int i=head[x]; i; i=edge[i].next)
        dfs(edge[i].to);
    R[x]=++t;
    //printf("L=%d R=%d
",L[x],R[x]);
}
void clear()
{sz=1;for (int i=0; i<26; i++) son[0][i]=1;}
void buildTrie()
{
    int x=1,id=0;
    for (int i=0; i<len; i++)
        if (opt[i]=='P') loc[++id]=x;
            else if (opt[i]=='B') x=fa[x];
                else 
                    {
                        if (!son[x][opt[i]-'a'])
                             son[x][opt[i]-'a']=++sz,fa[sz]=x;
                         x=son[x][opt[i]-'a'];
                    }
}
void buildFail()
{
    int he=0,ta=1; q[0]=1; fail[1]=0;
    while (he<ta)
        {
            int now=q[he++];
            for (int i=0; i<26; i++)
                {
                    if (!son[now][i]) continue;
                    int ff=fail[now];
                    while (!son[ff][i]) ff=fail[ff];
                    fail[son[now][i]]=son[ff][i];
                    q[ta++]=son[now][i];
                }
        }
    for (int i=1; i<=sz; i++)
        addedge(fail[i],i);
}
void work()
{
    int now=1,id=0;
    change(L[1],1);
    for (int i=0; i<len; i++)
        if (opt[i]=='P')
            {
                id++;
                for (int j=heada[id]; j; j=ask[j].next)
                    ans[j]=query(R[loc[ask[j].to]])-query(L[loc[ask[j].to]]-1);
            }
        else if (opt[i]=='B') change(L[now],-1),now=fa[now];
            else now=son[now][opt[i]-'a'],change(L[now],1);
}    
//ACMachine
int main()
{    
    clear(); 
    scanf("%s",opt); len=strlen(opt);
    buildTrie(); buildFail();
    m=read();
    for (int x,y,i=1; i<=m; i++)
        x=read(),y=read(),addask(x,y,i);
    dfs(0); work();
    for (int i=1; i<=m; i++)
        printf("%d
",ans[i]);
    return 0;
}

BZOJ数组大小被坑艰辛史

原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/5352189.html