[BZOJ 3881] [Coci2015] Divljak

本文介绍了一种使用AC自动机解决字符串匹配问题的方法,特别是在处理大量字符串和实时查询的场景中。通过构建AC自动机,可以有效地进行批量字符串匹配,并通过优化的数据结构和算法实现快速查询。

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

BZOJ传送门

题目描述

Alice有 n n n个字符串 S 1 , S 2 . . . S n S_1,S_2...S_n S1,S2...Sn,Bob有一个字符串集合 T T T,一开始集合是空的。

接下来会发生 q ​ q​ q个操作,操作有两种形式:

1 P,Bob往自己的集合里添加了一个字符串 P P P

2 x,Alice询问Bob,集合 T T T中有多少个字符串包含串 S x S_x Sx。(我们称串 A A A包含串 B B B,当且仅当 B B B A A A的子串)

Bob遇到了困难,需要你的帮助。

输入输出格式

输入格式

1 1 1行,一个数 n n n

接下来 n n n行,每行一个字符串表示 S i S_i Si

下一行,一个数 q q q

接下来 q q q行,每行一个操作,格式见题目描述。

输出格式

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

输入输出样例

输入样例#1:
3
a
bc
abc
5
1 abca
2 1
1 bca
2 2
2 3
输出样例#1:
1
2
1

提示

【数据范围】

1 ≤ n , q ≤ 100000 1 \le n,q \le 100000 1n,q100000

Alice和Bob拥有的字符串长度之和各自都不会超过 2000000 2000000 2000000

字符串都由小写英文字母组成。

解题分析

首先, 在线向 A C AC AC自动机添加 T T T的串是不科学的, 我们考虑建立出 S S S A C AC AC自动机然后在上面跑 T T T的串, 统计哪些 S S S中的串中出现过这个串。

如何判定一个前缀是否包含一个 S S S中的串? 如果 f a i l fail fail树上某个祖先是这个串的结尾节点, 那么就出现过一次。

因此, 实际上我们求的是若干位置(插入时在 A C AC AC自动机里每步走到的位置)到根节点的链的并。 直接求出 D F S DFS DFS序, 把这些位置按 D F S DFS DFS序排序, 在下面贡献 + 1 +1 +1, 在 L C A LCA LCA处贡献 − 1 -1 1,然后求子树和即可, 这个用个 B I T BIT BIT就好了。

注意倍增数组大小为 l o g ( n ) ​ log(n)​ log(n)的一维开在后面, 访问连续内存(否则要TLE)。 当然树剖 L C A ​ LCA​ LCA更快。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <iostream>
#define R register
#define IN inline
#define W while
#define lbt(i) ((i) & (-(i)))
#define gc getchar()
#define MX 2005000
#define SIZ 100500
int n, q, len, cnt, dfn, arr, bd;
int fail[MX], son[MX][26], end[SIZ];
int lb[MX], rb[MX], tree[MX], dep[MX], fat[MX][22], head[MX];
int buf[MX];
char str[MX];
std::queue <int> que;
struct Edge {int to, nex;} edge[MX];
IN bool cmp(R int x, R int y) {return lb[x] < lb[y];}
IN void insert(R int ID)
{
	int len = std::strlen(str), now = 0, id;
	for (R int i = 0; i < len; ++i)
	{
		id = str[i] - 'a';
		if (!son[now][id]) son[now][id] = ++cnt;
		now = son[now][id];
	}
	end[ID] = now;
}
IN void modify(R int pos, R int val) {for (; pos <= bd; pos += lbt(pos)) tree[pos] += val;}
IN int query(R int pos)
{
	int ret = 0;
	for (; pos; pos -= lbt(pos)) ret += tree[pos];
	return ret;
}
IN void add(R int from, R int to) {edge[++arr] = {to, head[from]}, head[from] = arr;}
void Getfail()
{
	R int now, i; fail[0] = -1;
	for (i = 0; i < 26; ++i) if (son[0][i]) que.push(son[0][i]);
	W (!que.empty())
	{
		now = que.front(); que.pop();
		for (i = 0; i < 26; ++i)
		{
			if (son[now][i]) fail[son[now][i]] = son[fail[now]][i], que.push(son[now][i]);
			else son[now][i] = son[fail[now]][i];
		}
	}
	for (R int i = 1; i <= cnt; ++i) add(fail[i], i);
}
void DFS(R int now)
{
	for (R int i = 1; i <= 21; ++i)
	{
		fat[now][i] = fat[fat[now][i - 1]][i - 1];
		if (!fat[now][i]) break;
	}
	lb[now] = ++dfn;
	for (R int i = head[now]; i; i = edge[i].nex)
	fat[edge[i].to][0] = now, dep[edge[i].to] = dep[now] + 1, DFS(edge[i].to);
	rb[now] = dfn;
}
IN int lca(R int x, R int y)
{
	if (dep[x] < dep[y]) std::swap(x, y);
	int del = dep[x] - dep[y], tim = 0;;
	W (del)
	{
		if (del & 1) x = fat[x][tim];
		++tim, del >>= 1;
	}
	if (x == y) return x;
	for (tim = 21; ~tim; --tim)
	if (fat[x][tim] ^ fat[y][tim])
	x = fat[x][tim], y = fat[y][tim];
	return fat[x][0];
}
IN void insert()
{
	int len = std::strlen(str), now = 0, id, tot = 0, tar;
	for (R int i = 0; i < len; ++i)
	{
		id = str[i] - 'a';
		now = son[now][id];
		buf[++tot] = now;
	}
	std::sort(buf + 1, buf + 1 + tot, cmp);
	tot = std::unique(buf + 1, buf + 1 + tot) - buf - 1;
	modify(lb[buf[1]], 1);
	for (R int i = 2; i <= tot; ++i)
	{
		tar = lca(buf[i - 1], buf[i]);
		modify(lb[buf[i]], 1);
		modify(lb[tar], -1);
	}
}
int main(void)
{
	using std::cin; using std::cout; using std::endl;
	int typ, pos;
	std::ios_base::sync_with_stdio(false);
	cin >> n;
	for (R int i = 1; i <= n; ++i) cin >> str, insert(i);
	bd = cnt + 1; Getfail(); DFS(0);
	cin >> q;
	for (R int i = 1; i <= q; ++i)
	{
		cin >> typ;
		if (typ & 1) cin >> str, insert();
		else cin >> pos, cout << query(rb[end[pos]]) - query(lb[end[pos]] - 1) << endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值