题目大意
给
N
N
N个操作。
1.插入一个串
S
S
S。
2.删除一个串
S
S
S 。
3.询问未被删除的串在
S
S
S中出现的次数。
∑
∣
S
∣
≤
3
e
5
\sum |S| \leq 3e5
∑∣S∣≤3e5,强制在线。
Solution
此题有多种解法,这里例举说明几种。(设 L L L为总长)
Solution 1
如果不强制在线,那么
A
C
AC
AC自动机,后缀自动机都可以做此问题。(对于删除可以再建一个专门的删除的串的自动机,然后答案相减)现在考虑如果强制在线,发现并不好维护,但是由于每个串之间的贡献相互独立,所以可以用到一个很好的技巧:二进制分组。
大意是这样的:假设现在有
q
q
q个串,我们维护
l
o
g
log
log个自动机,对于q的二进制
q
0
q
1
.
.
.
q
x
q_0q_1...q_x
q0q1...qx,如果
q
i
q_i
qi为
1
1
1,那么第
i
i
i个自动机就是
2
i
2^i
2i个串的自动机。相当于说将
q
q
q个串分到
l
o
g
log
log个组中,分组的依据就是
q
q
q的二进制拆分。那么假设我们现在要插入第
q
+
1
q+1
q+1个串,那么类似二进制进位,每次暴力重构即可。
具体的,(在插入
q
+
1
q+1
q+1之前)如果第
0
0
0个自动机存了
1
1
1个串,那么将其合并到第
1
1
1个自动机(同时清空第
0
0
0个自动机),在这之后,如果第
1
1
1个自动机存了
2
2
2个串,那么将其合并到第
2
2
2个自动机(同时清空第
1
1
1个自动机),依次类推。每次合并直接暴力重构。
复杂度分析也很明显,一个串最多被重构
l
o
g
log
log次(因为每次重构都要向后移动一位,只有
l
o
g
log
log位),所以复杂度是对的。
Solution 1.1
用 A C AC AC自动机即可
Solution 1.2
用后缀自动机。
Solution 2
考虑用后缀自动机怎么做,是不是每次查询
f
a
i
l
fail
fail树上到根的链?所以我们用
l
c
t
lct
lct维护
f
a
i
l
fail
fail树即可。
注意在线构造后缀自动机要判这个节点是否存在以及在线构造复杂度上界是
O
(
L
L
)
O(L\sqrt{L})
O(LL)。
复杂度
O
(
L
L
+
L
l
o
g
L
)
O(L \sqrt{L} + LlogL)
O(LL+LlogL)
Solution 3
考虑根号分治,常见的,如果一个串长度大于 L \sqrt{L} L,那么这样的串不会超过 L \sqrt{L} L个,否则其长度会小于 L \sqrt{L} L。
Solution 3.1
对于插入的一个串,如果长度大于
L
\sqrt{L}
L,那么对其建出
K
M
P
KMP
KMP数组。否则将其插入
t
r
i
e
trie
trie中。
对于查询,对于长度大于根号的,直接
K
M
P
KMP
KMP跑,注意此时复杂度是
L
×
l
e
n
a
s
k
\sqrt{L} \times len_{ask}
L×lenask。然后对于长度小于根号的,直接枚举查询串的一个后缀,然后直接在
t
r
i
e
trie
trie上走,注意到这里
t
r
i
e
trie
trie的深度小于
L
\sqrt{L}
L。
所以总的复杂度
L
L
L\sqrt{L}
LL
Solution 3.2
考虑后缀自动机。对于一个串,如果其长度小于 L \sqrt{L} L,那么直接在自动机上跳 f a i l fail fail暴力查询,这样复杂度为 l e n 2 len^2 len2。否则直接在后缀自动机上把相关的链的值重新算一遍(即 d f s dfs dfs一遍),这种操作不超过 L \sqrt{L} L次。所以总的复杂度还是 L \sqrt{L} L。