阿狸的打字机 Trie fail树+树状数组+dfs序

https://ac.nowcoder.com/acm/problem/17633

题意就不说了,那上面说得很清楚了;

这道题最暴力的办法就是,每个字符串都跑next数组,然后kmp匹配,数据很小的时候,可以这样,但不过这道题就不能了,太暴力了。

所以我们还是想想Trie(AC自动机吧),这道题用到了一个很有用的结论,我觉得这个结论在字符串配中还是很重要的

T串为S串的子串,当且仅当T是S的某一个前缀的后缀,这样的前缀的数目的多少,就是T在S中出现的次数。

而fail的指针跳的就是一个前缀的最长匹配后缀

我们这样做,从y字符串方向走fail,有多少个x的结束点就出现了多少次,直接在自动机上条跳fail绝对超时,不管你用什么优化,(dalao除外。。),首先建一颗fail树,有的人可能不会建fail树,其实也不难,就是fail指向跳到他的点,其实就是把fail指针反向。然后把y上的所有结点变成1,然后求fail的树中x的结点的子树中有多个数1,这个东西就可以用dfs序,加上树状数组维护了,这种方法在AC自动机的字符串题目中比较常见。对于一个P操作,就是打印一个串,我们直接把串的最后一个字符的记录下来,用一个pos数组表示字符串的编号,p数组表示,他在Trie上的位置,B操作就是返回他的父亲结点。

在求解答案的时候,当加入一个点时,就对in全部加1,然后查找与他的有关的x串,这个的ans就是ou[x的位置]-in[x的位置-1];这样遍历Trie就可以了。

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int tree[N];
int lowbits(int x){return x&(-x);}
void add(int x,int val)
{
    for(;x<N;x+=lowbits(x)) tree[x]+=val;
}
int query(int x)
{
    int ret=0;
    for(;x;x-=lowbits(x)) ret+=tree[x];
    return ret;
}
char str[N];
int fa[N],pos[N],p[N],num;
vector<int>son[N];
int nxt[N][26],fail[N];
int tot=0;
void Insert(char *s)
{
    int len=strlen(s),now=0;
    num=0;
    for(int i=0;i<len;i++)
    {
        int x=s[i]-'a';
        if(s[i]>='a'&&s[i]<='z')
        {
            if(!nxt[now][x]) nxt[now][x]=++tot,fa[nxt[now][x]]=now;
            now=nxt[now][x];
        }
        else if(s[i]=='P')
            pos[now]=++num,p[num]=now;
        else
            now=fa[now];
    }
}
void build()
{
    queue<int>qu;
    for(int i=0;i<26;i++)
        if(nxt[0][i]) qu.push(nxt[0][i]);
    while(!qu.empty())
    {
        int u=qu.front();qu.pop();
        for(int i=0;i<26;i++)
        {
            if(nxt[u][i]!=0) fail[nxt[u][i]]=nxt[fail[u]][i],qu.push(nxt[u][i]);
            else nxt[u][i]=nxt[fail[u]][i];
        }
    }
}
int in[N],ou[N],dfn=0;
vector<int>vec[N];
void dfs(int u,int fa)
{
    in[u]=++dfn;
    for(int i=0;i<vec[u].size();i++)
    {
        int v=vec[u][i];
        if(v==fa) continue;
        dfs(v,u);
    }
    ou[u]=dfn;
}
vector<pair<int,int> >pa[N];
int x[N],y[N],m,ans[N];
void solve(int u)
{
    add(in[u],1);
    for(int i=0;i<pa[pos[u]].size();i++)
    {
        int x=pa[pos[u]][i].first,id=pa[pos[u]][i].second;
        ans[id]=query(ou[p[x]])-query(in[p[x]]-1);
    }
    for(int i=0;i<son[u].size();i++)
    {
        int v=son[u][i];
        solve(v);
    }
    add(in[u],-1);
}
int main()
{
    memset(tree,0,sizeof(tree));
    memset(fail,0,sizeof(fail));
    memset(nxt,0,sizeof(nxt));
    scanf("%s",str);
    Insert(str);
    for(int i=0;i<=tot;i++)
        for(int j=0;j<26;j++)
            if(nxt[i][j]) son[i].push_back(nxt[i][j]);
    build();
    for(int i=1;i<=tot;i++)
    {
        vec[fail[i]].push_back(i);
        //vec[i].push_back(fail[i]);
    }
    dfs(0,0);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x[i],&y[i]);
        pa[y[i]].push_back(make_pair(x[i],i));
    }
    solve(0);
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
    return 0;
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值