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

来源:互联网 发布:网路优化 编辑:程序博客网 时间:2024/05/18 00:27

阿狸的打字机

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;}
阅读全文
0 0