2434: [Noi2011]阿狸的打字机 fail树+dfs序+树状数组

一天内做了两道AC自动机两道fail树的题目(好吧本质都一样啊就是fail树的不同应用→ →),感觉对fail树的理解逐渐加深。
对于这道题目,即给你N个字符串,每次给定两个字符串x,y,询问x在y中出现的次数。
一个直接的暴力思路是做M遍KMP,复杂度肯定爆表啦。。也就40分。
我们考虑fail树,如果y中有一个节点i指向了x的尾节点,证明字符串y中以I为结尾的后缀和字符串x中以x的尾节点结尾的前缀相同,而以x的尾节点结尾的前缀正是x串,也就出现了一次。那么知道了这一点,y中如果有ans个节点的fail指针指向x的尾节点,那么答案就是这个ans了。
我们可以构建出这棵fail树,因为每个点的出度为1,如果把边反向,我们就会得到一棵树了。而上文提到的ans就是以x的尾节点为根的子树中y中节点出现的次数,这个画画图就知道了。
于是我再次想到了暴力(我太弱啦QAQ),每次暴力dfs,看起来这样还是可以过70分的数据的。


然后我脑洞就开到了这里。。默默地看了题解。。QAQ
我居然连dfs序都没想到。。。

PoPoQQQ:我们把关于y的询问都存在邻接表里
然后把y所有的节点在DFS序中的位置插入树状数组,然后对于关于y的每个询问在树状数组上查询一遍即可。

感觉好机智啊,直接离线处理。

求出DFS序之后 我们回来考虑这些操作序列

尾添加一个字符->添加的字符所在节点加入树状数组
在结尾删除一个字符->删除的字符所在节点从树状数组删除
打印当前字串-> 处理询问
处理询问

真是一道好题啊,最后1A了赞。

#include<cstdio>
#define N 100005
#define lowbit(i) (i&(-i))
#include<cstring>
using namespace std;
int len,m,cnt,sum,dfn;
int in[N],out[N],tree[N<<1];
int head[N],head_[N],next[N],next_[N],list[N],list_[N],pos[N],ans[N],fa[N],p[N],q[N],a[N][26];
char s[N];
inline int read()
{
    int a=0,f=1; char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();}
    while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();}
    return a*f;
}
inline void insert(int x,int y)
{
    next[++sum]=head[x];
    head[x]=sum;
    list[sum]=y;
}
inline void add(int x,int y,int tot)
{
    next_[tot]=head_[x];
    head_[x]=tot;
    list_[tot]=y;
}
inline void build_tree()
{
    cnt=1; 
    int x=1,id=0;
    for (int i=0;i<26;i++) a[0][i]=1;
    for (int i=0;i<len;i++)
        switch(s[i])
        {
            case 'B':
                x=fa[x];
                break;
            case 'P':
                pos[++id]=x;
                break;
            default:
                if (!a[x][s[i]-'a']) a[x][s[i]-'a']=++cnt,fa[cnt]=x;
                x=a[x][s[i]-'a'];
                break;
        }
}
inline void build_fail()
{
    int t=0,w=1,x;
    q[1]=1; p[1]=0;
    while (t<w)
    {
        x=q[++t];
        for (int i=0;i<26;i++)
            if (a[x][i])
            {
                int k=p[x];
                while (!a[k][i]) k=p[k];
                p[a[x][i]]=a[k][i];
                q[++w]=a[x][i];
            }
    }
}
void dfs(int x)
{
    in[x]=++dfn;
    for (int i=head[x];i;i=next[i]) dfs(list[i]);
    out[x]=++dfn;
}
inline void Add(int x,int val)
{
    for (int i=x;i<=dfn;i+=lowbit(i)) tree[i]+=val;
}
inline int query(int x)
{
    int tmp=0;
    for (int i=x;i;i-=lowbit(i)) tmp+=tree[i];
    return tmp;
}
inline void solve()
{
    int x=1,id=0;
    Add(in[1],1);
    for (int i=0;i<len;i++)
        switch(s[i])
        {
            case 'P':
                for (int j=head_[++id];j;j=next_[j])
                {
                    int t=pos[list_[j]];
                    ans[j]=query(out[t])-query(in[t]-1);
                }
                break;
            case 'B':
                Add(in[x],-1);
                x=fa[x];
                break;
            default:
                x=a[x][s[i]-'a'];
                Add(in[x],1);
                break;
        }
}               
int main()
{
    scanf("%s",s);
    len=strlen(s);
    build_tree();
    build_fail();
    for (int i=1;i<=cnt;i++) insert(p[i],i);
    m=read();
    for (int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        add(v,u,i);
    }
    dfs(0);
    solve();
    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、付费专栏及课程。

余额充值