bzoj 3881: [Coci2015]Divljak AC自动机+树链的并+树状数组

本文介绍使用AC自动机解决字符串查询问题的方法。通过构建AC自动机和差分+树状数组,实现高效查询一个字符串集合中包含特定子串的数量。文章详细解释了解题思路及实现细节。

题意

Alice有n个字符串S_1,S_2…S_n,Bob有一个字符串集合T,一开始集合是空的。
接下来会发生q个操作,操作有两种形式:
“1 P”,Bob往自己的集合里添加了一个字符串P。
“2 x”,Alice询问Bob,集合T中有多少个字符串包含串S_x。(我们称串A包含串B,当且仅当B是A的子串)
Bob遇到了困难,需要你的帮助。
1 <= n,q <= 100000;
Alice和Bob拥有的字符串长度之和各自都不会超过 2000000;
字符串都由小写英文字母组成。

分析

挺牛逼的一道题。

先对S建AC自动机,得到fail树。然后对于每加入的一个串,在AC自动机上跑。朴素的想法就是,每跑到一个点就把fail树上这个点到根的路径每个点的答案都+1。而对于每次操作每个点只能被加一次。
注意到每次操作的点在fail树上都是若干条从某节点到根的路径的并,有种骚操作就是把每条链的端点按照dfs序排序,然后把每个点到根路径上的点+1,相邻两个点的lca到根路径上的点-1。这里画图感受一下就明白了。
这里路径加可以用差分+树状数组实现。

本以为打起来会很麻烦,但其实也还好,一打完RE了一次之后就A了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;

const int N=2000005;

int n,m,a[N],dfn_l[N],dfn_r[N],c[N],ch[N][26],sz,cnt,last[N],fa[N],dep[N],rmq[N*2][25],bin[25],tim,dfn_tim,arr[N],lg[N*2],num[100005];
char str[N];
queue<int> que;
struct edge{int to,next;}e[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

bool cmp(int x,int y) {return dfn_l[x]<dfn_l[y];}

void addedge(int u,int v) {e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;}

void ins(int x,int y) {while (x<=dfn_tim) c[x]+=y,x+=x&(-x);}

int query(int x) {int ans=0;while (x) ans+=c[x],x-=x&(-x);return ans;}

void get_fail()
{
    for (int i=0;i<26;i++) if (ch[0][i]) que.push(ch[0][i]);
    while (!que.empty())
    {
        int u=que.front();que.pop();
        for (int i=0;i<26;i++)
            if (ch[u][i]) fa[ch[u][i]]=ch[fa[u]][i],que.push(ch[u][i]);
            else ch[u][i]=ch[fa[u]][i];
        addedge(fa[u],u);
    }
}

void dfs(int x)
{
    dep[x]=dep[fa[x]]+1;dfn_tim++;dfn_l[x]=dfn_tim;
    tim++;rmq[tim][0]=x;arr[x]=tim;
    for (int i=last[x];i;i=e[i].next)
    {
        dfs(e[i].to);
        tim++;rmq[tim][0]=x;
    }
    dfn_r[x]=dfn_tim;
}

void get_rmq()
{
    for (int i=1;i<=tim;i++) lg[i]=log(i)/log(2);
    for (int j=1;j<=lg[tim];j++)
        for (int i=1;i+bin[j]-1<=tim;i++)
            rmq[i][j]=dep[rmq[i][j-1]]<dep[rmq[i+bin[j-1]][j-1]]?rmq[i][j-1]:rmq[i+bin[j-1]][j-1];
}

int get_lca(int x,int y)
{
    int l=min(arr[x],arr[y]),r=max(arr[x],arr[y]),w=lg[r-l+1];
    return dep[rmq[l][w]]<dep[rmq[r-bin[w]+1][w]]?rmq[l][w]:rmq[r-bin[w]+1][w];
}

int main()
{
    bin[0]=1;
    for (int i=1;i<=20;i++) bin[i]=bin[i-1]*2;
    n=read();
    for (int i=1;i<=n;i++)
    {
        scanf("%s",str);
        int len=strlen(str),now=0;
        for (int j=0;j<len;j++)
            if (!ch[now][str[j]-'a']) now=ch[now][str[j]-'a']=++sz;
            else now=ch[now][str[j]-'a'];
        num[i]=now;
    }
    get_fail();
    dfs(0);
    get_rmq();
    m=read();
    while (m--)
    {
        int op=read();
        if (op==1)
        {
            scanf("%s",str);
            int len=strlen(str),now=0,tot=0;
            for (int i=0;i<len;i++) now=ch[now][str[i]-'a'],a[++tot]=now;
            sort(a+1,a+tot+1,cmp);
            ins(dfn_l[a[1]],1);
            for (int i=2;i<=tot;i++)
            {
                int lca=get_lca(a[i],a[i-1]);
                ins(dfn_l[a[i]],1);ins(dfn_l[lca],-1);
            }
        }
        else
        {
            int x=read();x=num[x];
            printf("%d\n",query(dfn_r[x])-query(dfn_l[x]-1));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值