bzoj3881 [Coci2015]Divljak

本文介绍了一种使用AC自动机解决字符串匹配问题的方法,包括构建AC自动机、处理字符串集合中的匹配查询等关键步骤,并提供了一段完整的C++实现代码。

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

http://www.elijahqi.win/archives/2910
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
HINT

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

鸣谢 Dzy

首先将alice的集合建AC自动机 那么每次添加bob的集合的时候可以知道去ac自动机上跑一下 每个经过的节点的fail树上的那条链都需要添加一下计数器 但是这样暴力跳fail链复杂度是不对的大概最坏会是n^2的复杂度 那么相当于我需要计算链的这个并 那么就有一个神奇的做法 首先把我这次这个单词要经过的AC自动机上所有点按照dfs序排序 那么给每个点都+1 然后统计的时候直接统计该单词子树里+1的个数即可 但是可能会有重复的所以需要把lca处都-1 然后即可

#include<queue>
#include<cstdio>
#include<cstring> 
#include<algorithm>
#define N 2000010
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
    if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
    *oh++ = c;
}
template<class T>
inline void W(T x) {
    static int buf[30], cnt;
    if (x == 0) write_char('0');
    else {
        if (x < 0) write_char('-'), x = -x;
        for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
        while (cnt) write_char(buf[cnt--]);
    }
}
inline void flush() {
    fwrite(obuf, 1, oh - obuf, stdout);
}
int cnt=1,num,trans[N][26],ed[N],in[N],out[N],fail[N],dep[N],fa[N][20],Log[N];
int n,S[N],h[N];char s[N];
struct node{
    int y,next;
}data[N];
inline void insert1(int x,int y){
    data[++num].y=y;data[num].next=h[x];h[x]=num;
}
inline void buildtr(char *s,const int &id){
    int len=strlen(s+1),p=1;
    for (int i=1,nxt;i<=len;++i){
        if (!trans[p][s[i]-'a']) trans[p][s[i]-'a']=nxt=++cnt;
        else nxt=trans[p][s[i]-'a'];p=nxt;
    }ed[id]=p;
}
inline void buildAC(){
    queue<int>q;q.push(1);for (int i=0;i<26;++i) trans[0][i]=1;
    while(!q.empty()){
        int x=q.front();q.pop();
        for (int i=0;i<26;++i){
            int &y=trans[x][i];
            if (y) fail[y]=trans[fail[x]][i],q.push(y);
            else {y=trans[fail[x]][i];continue;}insert1(fail[y],y);
        }
    }
}
inline int lca(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);int dis=dep[x]-dep[y];
    for (int i=0;i<=Log[dis];++i) if (dis&(1<<i)) x=fa[x][i];
    if(x==y) return x;
    for (int i=Log[dep[y]];~i;--i)
        if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
inline void add(int x,int v){for (register int i=x;i<=cnt;i+=i&-i) S[i]+=v;}
inline int query(int x){int tmp=0;while(x) tmp+=S[x],x-=x&-x;return tmp;}
inline void dfs(int x){
    in[x]=++num;//printf("%d\n",x);
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y;if (y==fa[x][0]) continue;fa[y][0]=x;
        dep[y]=dep[x]+1;for (int j=1;j<=Log[dep[y]];++j) fa[y][j]=fa[fa[y][j-1]][j-1];dfs(y);
    }out[x]=num;
}int q[N];
inline bool cmp(const int &a,const int &b){return in[a]<in[b];}
int main(){
    freopen("bzoj3881.in","r",stdin);
    n=read();for (int i=1;i<=n;++i) scanf("%s",s+1),buildtr(s,i);
    buildAC();Log[0]=-1;for (int i=1;i<=cnt;++i) Log[i]=Log[i>>1]+1;
    num=0;dfs(1);int m=read();
    for (int i=1;i<=m;++i){
        int op=read();if (op==1){scanf("%s",s+1);static int len,p,nm;
            p=1;len=strlen(s+1);nm=0;
            for (int j=1;j<=len;++j) p=trans[p][s[j]-'a'],q[++nm]=p;
            sort(q+1,q+nm+1,cmp);
            for (int j=1;j<nm;++j) add(in[lca(q[j],q[j+1])],-1);
            for (int j=1;j<=nm;++j) add(in[q[j]],1);continue;
        }static int id;id=read();W(query(out[ed[id]])-query(in[ed[id]]-1));write_char('\n');
    }flush();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值