ix35 @ 2021-06-15 15:47:40
因一次惨痛的教训来写这篇帖子。
今天参加联测时,有一道广义 SAM 裸题,本来随便打打就应该过了,结果没想到因为这道题的写挂,让我发现这辈子之前写过的所有广义 SAM 都是错的,同时也发现网络上一些犯有同样错误的博客以及可视化网站。
考虑如下两个字符串构成的广义后缀自动机:
iod
od
我们可以根据这两个字符串建出如下图所示的 Trie,后缀自动机,和 Parent 树。
Trie 中结点的数字表示这个点对应的后缀自动机上的结点。
然而,有一种不常被注意的错误如下图所示:
这种错误似乎是有些普遍的,我们打开洛谷模板题的第一篇题解,来自 @辰星凌 的博客:
https://www.luogu.com.cn/blog/ChenXingLing/solution-p6139
这篇题解中提到三种建立广义 SAM 的方法,并正确地指出了第二种的错误(这和上面我们看到的等价),但是他在第三种算法中说直接运行普通 SAM 的
我找到了博主的 AC 代码并测试,发现这份代码建出的后缀自动机果然有我上面所说的问题。
https://www.luogu.com.cn/record/36889179
接下来我找到了网上的一些后缀自动机可视化网站,抽选了百度出来的第一个结果,以及我平常用的一个网站,调查结果如下:
上图截自 https://mivik.gitee.io/sam-visualizer/,是正确的广义 SAM。
上图截自 https://yutong.site/sam/,犯了我上面所说的错误。
此外还有一些 SAM 可视化网站,如 http://wenhao801.com:5000/,这个网站在输入
下面我们简单讨论一下这个错误。
错误演示中,多了
为什么有这种错误无法发现?
在部分题目中,这样建立后缀自动机并不会影响答案的正确性,例如洛谷模板题:https://www.luogu.com.cn/problem/P6139。
题目要我们求出
而如果出现这种错误,由于
那么这有什么关系呢?
关系很大,它影响了 SAM 的基本结构。
SAM 中一个串只能属于一个结点,同时我们不允许一个点和它的
更严重的是,它影响了子树与后缀关系的判定,只要遇到需要通过子树操作刻画以某个串为后缀的所有串,上面的方法就出锅了,例如我今天做的那道题。
这一点在上面提到的博客中阐述的比较清楚(只是似乎作者没有意识到 Trie 树做法也有这个问题)。
正常来说,我们进行单串 SAM 的插入字符时,设之前的终止结点为
单串 SAM 情况下,
但广义 SAM 就不一样了,
发现了吗?问题就出在最后一句。
注意到此时由于
但如果按照一般 SAM 的写法,我们会直接返回
解决这个问题是简单的,插入时判定
当然,这样的解决不够漂亮,因为如最上面图中的
真正合理的解释是我们根本不需要建立
by ix35 @ 2021-06-15 15:48:46
@辰星凌
by XYY1411 @ 2021-06-15 15:54:34
投日报?
by meyi @ 2021-06-15 15:54:53
草,把刚学会的广义SAM板子测了一下,是错的,感谢 @ix35
by meyi @ 2021-06-15 15:56:06
我这辈子打的唯一一个广义SAM是错的/kk
by BurningEnderDragon @ 2021-06-15 16:01:31
@ix35 是否考虑投日报?
by Pecuria @ 2021-06-15 16:05:02
@ix35 后面不是说了如何建是完全正确的吗
反正我照着题解的做法写没有任何问题
而且我把题解代码测试了一下也没有问题
by Pecuria @ 2021-06-15 16:06:18
应该是您拿AC代码的时候正好拿成是错的那个吧
by ix35 @ 2021-06-15 16:11:35
@Tamaki_Iroha
他写的“离线构造”中的第三种方法没有特判,应当是错误的。
同时在他的另一篇博客 https://www.cnblogs.com/Xing-Ling/p/11755782.html 5.后缀自动机 中的 (3) 里面采用的写法是错的,我选择的也是他最新的提交记录。
by Pecuria @ 2021-06-15 16:11:38
@ix35 看了一下您拿的是离线dfs建SAM的代码,这个代码确实是错的。但是题解的代码是离线bfs建SAM,这个是对的。
不过题解也确实说了dfs在时间复杂度上是
by Pecuria @ 2021-06-15 16:17:22
@ix35 为什么我测他的代码就没问题啊