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\times (n-1)\) 次,它是由 fail 树的边数决定的。

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

时间复杂度 \(O(n \log n)\)

代码

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

#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 @ 2025-01-07 23:40  KS_Fszha  阅读(1)  评论(0编辑  收藏  举报