fail树 【Noi2011】 阿狸的打字机 bzoj2434

题目描述:

阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机。打字机上只有28个按键,分别印有26个小写英文字母和'B'、'P'两个字母。

经阿狸研究发现,这个打字机是这样工作的:

l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后)。

l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失。

l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失。

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

a

aa

ab

我们把纸上打印出来的字符串从1开始顺序编号,一直到n。打字机有一个非常有趣的功能,在打字机中暗藏一个带数字的小键盘,在小键盘上输入两个数(x,y)(其中1≤x,y≤n),打字机会显示第x个打印的字符串在第y个打印的字符串中出现了多少次。

阿狸发现了这个功能以后很兴奋,他想写个程序完成同样的功能,你能帮助他么?

题目分析:
这道题最简单粗暴的做法咧就是建一个AC自动机,然后每次在上面匹配串y,对于每个节点都蹦fail指针,能蹦到x答案就++,然后输出答案。正确性显然,但是很慢。

所以我们要考虑一个东西叫做fail树。
fail树就是建完AC自动机或者Trie图(我一直以为我以前写的是AC自动机,后来发现原来我写的全都是Tried图)后,把所有的fail边反过来,就是fail树。

这个fail树有什么用呢?根据它的性质,可以发现一个子树中所有的节点都有一个后缀与这个子树的跟所代表的串相同,也就是包含这个串。

这样我们可以维护一个fail树上的dfs出栈入栈序,然后再在AC自动机上做DFS,每次找到当前节点在fail树dfs序上的位置进行修改,维护前缀和,用树状数组比较好。
搜到y节点的时候就查询x节点的入栈和出栈两个位置中间共出现过多少个节点即可。
这样我们就保证了计算答案的所有节点都在x的子树中,并且都在y到x的路径上,正确性显然。

时间复杂度O(nlogn)

注意:
因为我写的是Trie图而不是AC自动机,所以在结构体中我又开了一个dep代表,DFS的搜索顺序是按照Trie树的顺序。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 120000
using namespace std;
inline int lowbit(int x) { return x & -x; }
int mark;
struct Trie{
    Trie *son[26],*fail,*fa;
    int num,dep;
    bool vis;
    Trie(Trie *fa=0x0,int num=0,int dep=0):fa(fa),num(num),vis(false),dep(dep),fail(0x0){ memset(son,0,sizeof(son)); }
    void insert(int x)  { if(!son[x])  son[x]=new Trie(this,++mark,dep+1); }
}*root=new Trie,*now=root;
int fw[N<<1];
int pos_first[N],pos_second[N],top;
int a[N],tot,ans[N];
char s[N];
int n,x,y;
int fir[N],nes[N],v[N],cnt;
int ask_fir[N],ask_nes[N],ask_v[N];

void ask_edge(int x,int y,int num)
{
    ask_v[num]=y;
    ask_nes[num]=ask_fir[x];
    ask_fir[x]=num;
    return;
}
void edge(int x,int y)
{
    cnt++;
    v[cnt]=y;
    nes[cnt]=fir[x];
    fir[x]=cnt;
    return;
}
void Fail()
{
    static Trie* dl[N];
    int l=1,r=0;
    for(int i=0;i<26;i++)
        if(root->son[i])
        {
            root->son[i]->fail=root;
            edge(root->num,root->son[i]->num);
            dl[++r]=root->son[i];
        }
        else root->son[i]=root;

    Trie *c;
    while(l<=r)
    {
        c=dl[l++];
        for(int i=0;i<26;i++)
            if(c->son[i])
            {
                c->son[i]->fail=c->fail->son[i];
                edge(c->son[i]->fail->num,c->son[i]->num);
                dl[++r]=c->son[i];
            }
            else c->son[i]=c->fail->son[i];
    }
    return;
}
void change(int x,int y)
{
    for(;x<=(mark<<1)+10 ;x+=lowbit(x))
        fw[x]+=y;
    return;
}
int query(int x)
{
    int ans=0;
    for(;x;x-=lowbit(x)) ans+=fw[x];
    return ans;
}
void dfs(int c)
{
    pos_first[c]=++top;
    for(int t=fir[c];t;t=nes[t]) dfs(v[t]);
    pos_second[c]=++top;
}
void Trie_dfs(Trie *c)
{
    c->vis=true;
    change(pos_first[c->num],1);
    for(int t=ask_fir[c->num];t;t=ask_nes[t])
        ans[t]=query(pos_second[ask_v[t]])-query(pos_first[ask_v[t]]-1);
    for(int i=0;i<26;i++)
        if(!c->son[i]->vis && c->son[i]->dep==c->dep+1) Trie_dfs(c->son[i]);
    change(pos_second[c->num],-1);
}
int main()
{
    scanf("%s",s);
    for(int i=0;s[i];i++)
    {
             if(s[i]=='P') a[++tot]=now->num;
        else if(s[i]=='B') now=now->fa;
        else
        {
            now->insert(s[i]-'a');
            now=now->son[s[i]-'a'];
        }
    }
    Fail();
    dfs(0);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        ask_edge(a[y],a[x],i);
    }
    Trie_dfs(root);
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值