Luogu P2414 NOI2011 阿狸的打字机 题解 [ 紫 ] [ AC 自动机 ] [ 离线思想 ] [ 树状数组 ] [ dfs 序 ]

阿狸的打字机:非常牛的 AC 自动机题。

暴力

先考虑在暴力的情况下,我们如何计算 x 匹配 y 的次数。显然,我们会模拟往 y 里加字符的过程,在此过程中做 KMP 进行匹配,统计答案。

那么如果涉及多个模式串呢?就可以把 KMP 加强成 AC 自动机了。

考虑在 AC 自动机上如何刻画这个过程,在匹配过程中,我们从到达过的每一个点出发,往前暴力跳 fail 树,找出匹配 x 的后缀即可。

转化

y 的路线去找 x 非常的没有前途,我们考虑从 x 出发,能不能找到 y 的路线。题意转化为x 的子树与 y 到根链的交集包含的节点数。

x 能匹配上的串一定是它在 fail 树中子树的所有节点,这个“子树”就让人很容易想到用 dfs 序转化为序列上的区间问题来解决。那么我们如何处理 y 的路线呢?

观察

这个就涉及到本题很巧妙的一个性质了,本题给出的打印字符串实际上是可以动态维护的,我们考虑维护一个栈,里面存 AC 自动机上的指针,回退的时候 pop 即可。

在这个过程中,我们可以不断给新到达的节点的权值进行 +1,1 的操作,这个操作一共会进行大概 2×(n1) 次,它是由 fail 树的边数决定的。

那么接下来就是很简单的事情了,我们按 y 的大小将询问排序并离线下来,在动态维护字符串的过程中标记 y 到根节点的链,这个可以用树状数组或者线段树来进行这个单点修改,区间查询的操作。查询的时候就查子树的区间即可。

时间复杂度 O(nlogn)

代码

代码比较长,但是如果分模块完成的话应该不会很难实现。

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
vector<pi>qs[100005];
int n,m,p=0,ch[100005][30],ap[100005],idx=0,cnt=0,ne[100005],now=0;
int lx[100005],rx[100005],tr[100005],ans[100005];
char s[100005];
stack<int>stk;
vector<int>g[100005];
void build()
{
    queue<int>q;
    for(int i=0;i<26;i++)
    {
        if(ch[0][i])q.push(ch[0][i]);
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=0;i<26;i++)
        {
            int v=ch[u][i];
            if(v)ne[v]=ch[ne[u]][i],q.push(v);
            else ch[u][i]=ch[ne[u]][i];
        }
    }
}
void init()
{
    for(int i=1;i<=idx;i++)g[ne[i]].push_back(i);
}
void dfs(int u)
{
    lx[u]=++now;
    for(auto v:g[u])dfs(v);
    rx[u]=now;
}
int lowbit(int x){return (x&(-x));}
int query(int x)
{
    if(x<=0)return 0;
    int ans=0;
    while(x)
    {
        ans+=tr[x];
        x-=lowbit(x);
    }
    return ans;
}
void update(int x,int v)
{
    if(x<=0)x=1;
    while(x<=now)
    {
        tr[x]+=v;
        x+=lowbit(x);
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>s+1;
    n=strlen(s+1);
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        cin>>x>>y;
        qs[y].push_back({x,i});
    }
    stk.push(0);
    p=0;
    for(int i=1;i<=n;i++)
    {
        if(s[i]>='a'&&s[i]<='z')
        {
            int c=s[i]-'a';
            if(ch[p][c]==0)ch[p][c]=++idx;
            p=ch[p][c];
            stk.push(p);
        }
        else if(s[i]=='B')
        {
            stk.pop();
            p=stk.top();
        }
        else
        {
            ap[++cnt]=p;
        }
    }
    build();
    init();
    dfs(0);
    p=0;
    while(!stk.empty())stk.pop();
    stk.push(0);
    cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(s[i]>='a'&&s[i]<='z')
        {
            int c=s[i]-'a';
            p=ch[p][c];
            stk.push(p);
            update(lx[p],1);
        }
        else if(s[i]=='B')
        {
            update(lx[p],-1);
            stk.pop();
            p=stk.top();
        }
        else
        {
            cnt++;
            for(auto que:qs[cnt])
            {
                int x=que.fi,id=que.se;
                ans[id]=query(rx[ap[x]])-query(lx[ap[x]]-1);
            }
        }
    }    
    for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
    return 0;
}
posted @   KS_Fszha  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示