BZOJ 3881:Divljak (AC自动机+树链求并)

本文介绍了一道名为BZOJ3881:Divljak的问题解决方法,该问题涉及到字符串匹配和AC自动机的应用。通过构建AC自动机和维护一系列树链来优化查询效率,确保每次查询都能快速找到字符串集合中包含特定子串的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BZOJ 3881:Divljak

题目传送门
PS:万恶的权限题啊,为什么内存就这么小呢?MLE了无数次。。

【问题描述】

Description

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遇到了困难,需要你的帮助。

Input

第1行,一个数n;
接下来n行,每行一个字符串表示S_i;
下一行,一个数q;
接下来q行,每行一个操作,格式见题目描述。

Output

对于每一个Alice的询问,帮Bob输出答案。

Sample Input

3
a
bc
abc
5
1 abca
2 1
1 bca
2 2
2 3

Sample Output

1
2
1

【数据范围】
1 <= n,q <= 100000;
Alice和Bob拥有的字符串长度之和各自都不会超过 2000000;
字符串都由小写英文字母组成。

【解题思路】

  因为集合T是只增不减的。
  对S建AC自动机,构出fail树。
  f[i]表示当前集合T内有多少个串包含Si(只增不减)。
  每次加入新的串x,就更新f。
  让x在AC自动机上跑。
  得到的是一堆树链q[],因为匹配失败会跳fail。
  那么,由于每个S即使被包含,也只可被算一次。
  树链的并就是需要的答案。
  记g[i]为点i到root的路径
  将q[]按时间戳dfn从小到大排序。
  然后加上g [ q [ i ] ],减去g [ lca ( q [ i-1 ] , q [ i ] ) ]。
  为什么这样是正确的?
  画个图就可以证明,因为是按时间戳从小到大排的序。
  Si被包含的个数,其实就等同于Si的子树和(fail树)。
  求出进入点i的时间fi [ i ],离开点i的时间fo [ i ]。
  按dfn序建bit树Tr。
  那么,当前Sx的答案就是sumTr( fo [ i ] ) - sumTr( fi [ i ] - 1 )。
  
  注意:不要写RMQ-lca,会爆空间啊。用倍增lca就好……
【代码】

#include<cstdio>
#include<cmath>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))

#define depmin(a,b) ((dep[a]<dep[b])?(a):(b))

using namespace std;

typedef long long ll;

const int N=2000004;
const int kp=18;
int n,m;
int id[N],son[N][26],cnt;
int Tr[N],yg[N];
int fa[N][20],op[N];
int fd,fi[N],fo[N],tim,dep[N];
vector<int> gg[N];
char st[N];

bool cmp(int a,int b) { return (fi[a]<fi[b]); }

void addtrie(int yi)
{
    int sl=strlen(st); int now=0;
    for(int i=0;i<sl;i++)
    {
        int hy=st[i]-'a';
        if(!son[now][hy]) son[now][hy]=++cnt;
        now=son[now][hy];
    }
    id[yi]=now;
}

void buildfail()
{
    int head=1,tail=0;
    for(int i=0;i<26;i++)
    if(son[0][i]) op[++tail]=son[0][i];
    while(head<=tail)
    {
        int fw=op[head++];
        for(int j=0;j<26;j++)
        if(son[fw][j])
        {
            op[++tail]=son[fw][j];
            dep[son[fw][j]]=son[dep[fw]][j];
        } else son[fw][j]=son[dep[fw]][j];
    }
    for(int i=1;i<=tail;i++) gg[dep[op[i]]].push_back(op[i]);
}

void dfs(int x)
{
    fi[x]=++tim;
    for(vector<int>::iterator it=gg[x].begin();it!=gg[x].end();it++)
    {
        dep[*it]=dep[x]+1;
        fa[*it][0]=x;
        dfs(*it);
    }
    fo[x]=tim;
}

void read(int &x)
{
    x=0; char ch=getchar(); int f=1;
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(; isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
    x*=f;
}

int lca(int a,int b)
{
    if(dep[a]>dep[b]) swap(a,b);
    for(int i=kp;i>=0;i--)
    if(dep[fa[b][i]]>=dep[a]) b=fa[b][i];
    if(a==b) return a;
    for(int i=kp;i>=0;i--)
    if(fa[b][i]!=fa[a][i]) a=fa[a][i],b=fa[b][i];
    return fa[a][0];
}

void add(int a,int b) { for(int i=a;i<=tim;i+=(i&-i)) Tr[i]+=b; }

int query(int a)
{
    int s=0;
    for(int i=a;i;i-=i&-i) s+=Tr[i];
    return s;
}

int main()
{
    read(n); cnt=0;
    for(int i=1;i<=n;i++) scanf("%s",st),addtrie(i);
    buildfail(); tim=0; dep[0]=1; fa[0][0]=0; dfs(0);
    for(int i=1;i<=kp;i++)
    for(int j=1;j<=tim;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
    read(m);
    for(int i=1;i<=m;i++)
    {
        int ops; read(ops);
        if(ops==1)
        {
            scanf("%s",st);
            int sl=strlen(st); int yp=0;
            for(int j=0,now=0;j<sl;j++) yg[++yp]=now=son[now][st[j]-'a'];
            sort(yg+1,yg+1+yp,cmp);
            add(fi[yg[1]],1);
            for(int j=2;j<=yp;j++)
            {
                int lc=lca(yg[j],yg[j-1]);
                add(fi[lc],-1); add(fi[yg[j]],1);
            }
        } else
        {
            int ask; read(ask);
            int ans=query(fo[id[ask]])-query(fi[id[ask]]-1);
            printf("%d\n",ans);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值