[省选前题目整理][BZOJ 2434][NOI 2011]阿狸的打字机(AC自动机+fail树+DFS序+树状数组)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2434

思路

将每个打印出来的串插入AC自动机后可以发现,a串在b串中的出现次数,就是在AC自动机的fail树中a串的终止结点的子树中,包含了的b串节点的个数。
这是非常显然的,根据AC自动机的fail指针定义,一个代表前缀a的点的fail指向的前缀b,b一定是a最长的后缀。
这里写图片描述
如上图就是一个AC自动机,实边是trie的树边,虚边是fail树的树边。

那么在fail树中,三条虚边指向的那个结点的子树中,包含了三个左边那条链代表的串的结点,那么右边那条链代表的串就以最长后缀的形式在左边那条链的三个前缀中分别出现,也就是说右边那条链在左边那条链代表的串中出现了三次。

AC自动机和fail树都很好建,由于许多单词都有很长的公共前缀,因此可以顺着输入的字符串扫一遍,若碰到P就标记当前节点为危险节点,若碰到B就从当前节点爬到它的父结点,若碰到正常的a~z字母就向当前节点的儿子爬去。

那么问题就是要想办法支持查询一个子树中包含了多少个特定串的结点,最好的做法就是DFS序+树状数组。不妨以模拟前面所说的,刚开始建立trie的过程来离线处理所有的询问,为了方便叙述,我们把每个询问看成二元组(a,b),表示查询a串在b串中出现了多少次。我们维护一个序列,序列中每个元素代表DFS序中对应位置的点的出现次数n(或者说是这个点被n个不同的单词包含了),然后顺着输入的字符串扫一遍,若碰到B就从当前节点爬到它的父结点,并在BIT的对应位置减1,若碰到正常的a~z字母就向当前节点的儿子爬去,并将新的结点在BIT中的位置+1。若碰到P(访问一个终止结点x),就回答这个终止结点相关的所有询问二元组(a,x),其答案就是点a的子树在DFS序中对应区间的区间和。显然这样做,就能时刻保证BIT序列中只存在对应与当前正在扫的那个单词,其他单词的结点的出现次数已经全部被删去了,因此这个做法是正确的。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <vector>

#define MAXN 110000
#define MAXM 110000
#define lowbit(x) ((x)&(-(x)))

using namespace std;

vector<int>query[MAXM];
pair<int,int>info[MAXM];

struct edge
{
    int u,v,next;
}edges[MAXN];

int head[MAXN],nCount=0;

void AddEdge(int U,int V)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

struct Trie
{
    int ch[26],fail;
    int fa; //父亲指针,方便看到回车符时爬到父亲节点上去
}node[MAXN];

int nCount2=0;

int pos[MAXN],tot=0;

void ins(char *s) //插入一个单词s
{
    int len=strlen(s),p=0;
    for(int i=0;i<len;i++)
    {
        if(s[i]>='a'&&s[i]<='z')
        {
            if(!node[p].ch[s[i]-'a'])
            {
                node[p].ch[s[i]-'a']=++nCount2;
                node[node[p].ch[s[i]-'a']].fa=p; //!!!!!
            }
            p=node[p].ch[s[i]-'a'];
        }
        else if(s[i]=='B') //退格
            p=node[p].fa;
        else //打印,则点p是危险节点
            pos[++tot]=p;
    }
}

int q[MAXN*4],h=0,t=0;

void BuildAC()
{
    for(int i=0;i<26;i++)
        if(node[0].ch[i])
        {
            node[node[0].ch[i]].fail=0;
            q[t++]=node[0].ch[i];
        }
    while(h<t)
    {
        int now=q[h++];
        for(int i=0;i<26;i++)
            if(node[now].ch[i])
            {
                int p=node[now].fail;
                while(p!=0&&!node[p].ch[i]) p=node[p].fail;
                if(node[p].ch[i]) p=node[p].ch[i];
                node[node[now].ch[i]].fail=p;
                q[t++]=node[now].ch[i];
            }
    }
}

void BuildFail()
{
    for(int i=1;i<=nCount2;i++)
        AddEdge(node[i].fail,i);
}

int bit[MAXN];

int L[MAXN],R[MAXN],len=0;

void update(int x,int val)
{
    while(x<=len) //!!!!!
    {
        bit[x]+=val;
        x+=lowbit(x);
    }
}

int querySum(int x)
{
    int ans=0;
    while(x>0)
    {
        ans+=bit[x];
        x-=lowbit(x);
    }
    return ans;
}

void DFS(int u)
{
    L[u]=++len;
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        DFS(v);
    }
    R[u]=len;
}

char s[MAXN];

int main()
{
    int m;
    memset(head,-1,sizeof(head));
    scanf("%s",s);
    ins(s);
    BuildAC();
    BuildFail();
    DFS(0);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        query[pos[y]].push_back(i); //!!!!!
        info[i]=make_pair(pos[x],0);
    }
    int length=strlen(s);
    int p=0;
    for(int i=0;i<length;i++)
    {
        if(s[i]=='P') //更新询问
        {
            for(int j=0;j<(int)query[p].size();j++)
                info[query[p][j]].second=querySum(R[info[query[p][j]].first])-querySum(L[info[query[p][j]].first]-1);
        }
        else if(s[i]=='B') //更新树状数组
        {
            update(L[p],-1);
            p=node[p].fa;
        }
        else
        {
            p=node[p].ch[s[i]-'a'];
            update(L[p],1); //!!!!!!
        }
    }
    for(int i=1;i<=m;i++) printf("%d\n",info[i].second);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值