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 1≤n,q≤100000;
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;
}
}