前言
今天重新学了一下SAM
那么一些题目肯定是少不了的
在这里记录一下吧,可能没什么启发的题就不写了
bzoj
2946
题意:多个串的最长公共子串
题解:对于一个建立SAM,然后对于剩下的所有点在上面跑,对于每一个点的状态取最小值。但是这样是不够的,你还要跳一次fail来进行更新,这个的话拓扑更新就可以了
3998
题意:求第k大串
题解:直接预处理出每个点往下可以走多少个就可以了
1396
题意:定义一个字符x,他的识别子串是包含他且仅出现一次的子串,问每个字符的最小识别子串
题解:这题做出来了很开心啊,看来昨天不是白学的。考虑到对于一个点,仅出现一次就是right集合是1的点。我们对于这些点记录他的right集合,因为只有一个元素,这里我记为pos,然后考虑这个点,他可以表示的串是
[pos[x]−max[x]+1,pos[x]−min[x]+1]...pos[x]
[
p
o
s
[
x
]
−
m
a
x
[
x
]
+
1
,
p
o
s
[
x
]
−
m
i
n
[
x
]
+
1
]
.
.
.
p
o
s
[
x
]
可以知道如果左端点在
max[x]+1,pos[x]−min[x]+1]
m
a
x
[
x
]
+
1
,
p
o
s
[
x
]
−
m
i
n
[
x
]
+
1
]
,那么这个子串的长度就是
pos[x]−l+1
p
o
s
[
x
]
−
l
+
1
,当
pos[x]
p
o
s
[
x
]
取得最小的时候最小
如果是在
[pos[x]−min[x],pos[x]]
[
p
o
s
[
x
]
−
m
i
n
[
x
]
,
p
o
s
[
x
]
]
的话,那么这个子串的长度最小是
min[x]
m
i
n
[
x
]
的,这个的话当覆盖的子串
min[x]
m
i
n
[
x
]
最小的时候最小
用线段树维护这两个东西就可以了
2780
题意:n个串,m个串,问m个串每个在前n个串中的几个出现过。
题解:这题的话,显然是一个广义后缀自动机啊
然后维护,一个节点在多少个串里面出现过有两个方法
①:对于每加入一个节点,就标记一下他在当前串出现过,然后跳parent更新,如果遇到一个一级出现过了就退出。这样子可以记录出每一个节点有多少个串了。就可以随便做了。不知道怎么证明这个复杂度最差是
n−−√
n
的
②:对于每一个节点,记录有哪些子串包含这个节点,然后问题就转化问询问一段区间里面有多少种不同的数字,这个就是经典模型了。无论离线还是在线都是可以做到
nlogn
n
l
o
g
n
的
3277
题意:现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中,至少k个字符串的子串(注意包括本身)。
题解:广义后缀自动机就可以了。同意一下每个节点出现在了多少个串里面。要注意的是,每个节点要累加他父亲的答案,因为你范围更小的区间不在这里啊。然后有一个细节我跳了很久,就是我用的是对于新加入的点,跳parent更新这个点出现在了这个串里面,但是分裂的时候我没有把标记一起赋值,这个要注意
3238
题意:求所有后缀,两两之间lcp的和
题解::容易想到,把串反过来,就可以变成求所有前缀两两的最长公共后缀。我们知道,两个串的最长公共后缀,是
mx(LCA)
m
x
(
L
C
A
)
,然后遍历整棵树。考虑每个节点可以作为多少点对的LCA即可。至于有多少个代表前缀的点,每一个新增(分裂的不算)的节点都是一个,直接标记一下就好了
3879
题意:现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示),求这些后缀两两之间的LCP(LongestCommonPrefix)的长度之和.一对后缀之间的LCP长度仅统计一遍.
题解:和上一题基本上套路是一样的,但是这个有多个询问,建立虚树即可
4566
题意:给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
题解:
方法①:容易想到,如果我们建立广义后缀自动机,然后对于每一个节点分别记录在两个串里面出现了多少次,然后乘起来就可以了。
方法②:对于每一个串分别建立后缀自动机,然后在遍历他的字典树,如果对于x这个节点,如果两颗树都有y这个儿子,就一起走下去,复杂度是
(26n)
(
26
n
)
4556
题意:每个问题均有a,b,c,d四个参数,问你子串s[a..b]的所有子串和s[c..d]的最长公共前缀的长度的最大值是多少?
题解:第一次做这个题的时候,感觉很难,但现在看回来,也不过如此。最长公共前缀不是我们喜欢的,于是我们考虑,把串倒过来,那么就变成后缀了。
考虑二分答案mid
那么问题就变成了
[c..c+mid]
[
c
.
.
c
+
m
i
d
]
这一个子串有没有在[a,b]里面出现过
考虑找到一个节点,是的他的max是不小于mid的
这个的话可以用倍增来实现
然后找到这个节点之后,问题就剩下了询问这个节点的right集合时候有一个点是属于
[a...b]
[
a
.
.
.
b
]
线段树合并即可
4698
题意:给你n个序列,求他们的最长相同子串。相同子串的定义是他们这个子串全部加上某一个树可以和另外一个相同
题解:这题的话,查分一下就变成求最长公共子串了。
4180
题意:给你一个字符串,他只有ABCD四种字母。现在要求构造一个新的字符串S,构造的方法是:进行多次操作,每一次操作选择T的一个子串,将其加入S的末尾。问你能构造出的所有长度为N的字符串S中,构造所需的操作次数最大的字符串的操作次数。
题解:先考虑给你构造出来的串,你怎么求出来次数。一个显然的结论就是在SAM上面跑,跑到是被就ans++,然后跳回root。这个做法显然是最优的。于是我们就得到了一个做法,我们可以预处理出
mp[4][4]
m
p
[
4
]
[
4
]
,表示从i这个字母开始,一直走走到失配,跳回跟后走到j的最少步数,这个bfs一次就可以出来了。那么现在得到了一个4个点的完全图,问你走不超过n的路径,最多可以有多少条边。一个显然地贪心想法是中间肯定是很多个环包含在一起,但是贪心的话要讨论。我们使用一个比较无脑的方法。就是二分答案,二分用i条变,最少可以走距离为多少的路径,这个用倍增floyd可以轻松解决,然后就没有了。
要注意的一个细节是,我们预处理出的mp,有第一步是需要走第一个节点的,这个代价不可以漏了,然后一开始因为漏了这个加上二分姿势不太正确误打误撞过了样例,后来才反应过来