[BZOJ2434][NOI2011]阿狸的打字机-AC自动机

阿狸的打字机

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
aPaPBbP
3
1 2
1 3
2 3

Sample Output

2
1
0

HINT

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


1WA,原因:输出了打印次数个答案,而不是询问次数个答案,结果要么答案少一部分,要么多一堆迷之0……
还有这种操作……

(╯‵□′)╯︵┻━┻


思路:
首先,仔细观察题面,可以发现这个字符串在每个时间的状态都可以用一个栈表示。
那么:
插入一个字符等于在栈顶节点所在AC自动机上位置后插入一个儿子。
删除一个字符等于栈顶-1
打印所有字符等于将栈顶标记为一个字符串的结尾
然后咱就不需要把每个打印串分别求出并插入了~

然后,参见BZOJ3172,考虑使用fail树。
那么咱先对fail树求一遍dfs序,答案便是每个x节点的dfs区间中有多少个属于y的节点,这里属于y的节点的定义为,y串在AC自动机上的每个节点沿fail树走到根的路径上途径的所有点。

很显然,在dfs序上x节点的子树是一段连续的区间。
那么考虑再次模拟所有操作(其实就是对AC自动机进行dfs),使用一个树状数组维护。
每加入一个字符,就把在它dfs序上起点位置处给树状数组加上1。
每删除一个字符,就把刚才加的1减掉。
每遇到一次打印,那么便处理当前节点作为y时的所有询问:直接在树状数组上,对每个询问的x,查询其dfs序区间中的和为多少。
最后得到的查询结果即为在x子树中属于y节点的数量。
这就是答案了~

#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

const int N=1e5+9;

int n,m,t,ans[N],dfn;
int bit[N],stk[N],top;
char operate[N];
int to[N],nxt[N],id[N],beg[N],tot;

inline void push(int u,int v,int w)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    id[tot]=w;
    beg[u]=tot;
}

inline int lowbit(int x)
{
    return x&(-x);
}

inline void add(int x,int v)
{
    while(x<=dfn)
    {
        bit[x]+=v;
        x+=lowbit(x);
    }
}

inline int query(int x)
{
    int ret=0;
    while(x)
    {
        ret+=bit[x];
        x-=lowbit(x);
    }
    return ret;
}

struct AC_automaton
{
    int ch[N][28],fail[N],end[N],pool;
    int q[N],l,r,pos[N],st[N],ed[N];
    vector<int> g[N];

    inline int insert(int now,char c)
    {
        if(!ch[now][c-'a'])
            ch[now][c-'a']=++pool;
        return ch[now][c-'a'];
    }

    inline void set_end(int now,int id)
    {
        end[now]=id;
    }

    inline void getfail()
    {
        l=0;
        q[r=1]=0;
        fail[0]=0;

        while(l<r)
        {
            int u=q[++l];
            for(int i=0;i<26;i++)
                if(ch[u][i])
                {
                    q[++r]=ch[u][i];
                    fail[ch[u][i]]= u==0?0:ch[fail[u]][i];
                    g[fail[ch[u][i]]].push_back(ch[u][i]);
                }
                else
                    ch[u][i]= u==0?0:ch[fail[u]][i];
        }
    }

    inline void dfs(int u)
    {
        pos[u]=++dfn;
        if(end[u])
            st[end[u]]=dfn;
        for(int i=0,e=g[u].size();i<e;i++)
            dfs(g[u][i]);
        if(end[u])
            ed[end[u]]=dfn;
    }
}koishi;

int main()
{
    scanf("%s",operate+1);
    t=strlen(operate+1);

    stk[top=1]=0;
    for(int i=1;i<=t;i++)
        switch(operate[i])
        {
            case 'B':
                top--;
            break;
            case 'P':
                koishi.set_end(stk[top],++n);
            break;
            default:
                stk[++top]=koishi.insert(stk[top-1],operate[i]);
            break;
        }

    koishi.getfail();
    koishi.dfs(0);

    scanf("%d",&m);
    for(int i=1,x,y;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        push(y,x,i);
    }

    stk[top=1]=0;
    for(int i=1;i<=t;i++)
        switch(operate[i])
        {
            case 'B':
                add(koishi.pos[stk[top]],-1);
                stk[top--]=0;
            break;
            case 'P':
                for(int i=beg[koishi.end[stk[top]]];i;i=nxt[i])
                    ans[id[i]]=query(koishi.ed[to[i]])-query(koishi.st[to[i]]-1);
            break;
            default:
                stk[++top]=koishi.ch[stk[top-1]][operate[i]-'a'];
                add(koishi.pos[stk[top]],1);
            break;
        }

    for(int i=1;i<=m;i++)
        printf("%d\n",ans[i]);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值