省选级的串串入门

串串

很久以前因为 ABC 的 G 题出过两道后缀自动机,尝试自学,但是当时既不会 AC 自动机,也不会后缀数组,于是失败了。今年暑假集训的时候讲了 ACAM , SA , SAM。

这里只会记录一些有关字符串算法的题目,如果有谁看到了这篇文章,这只能为您指路,具体的还是要看 oiwiki 和洛谷题解。

后缀数组 SA

去看 oiwiki,挺好懂的。

后缀数组存了所有后缀,而将后缀按字典序排序是在比较他们的前缀。后缀的前缀就是字符串的所有字串,这样就可以维护一些信息。

求 height 数组用了这个 \(height_{rk_i} \ge height_{rk_{i - 1}} - 1\)。然后通过势能分析保证复杂度正确。

这样通过构造一组可行的方案,写出不等式,再通过势能分析来保证指针的移动不会超过 \(O(n)\) 次的方法之前遇到过。P3546这题,不过那题是求border就是了。

\[lcp(sa_i , sa_j) = \min_{i+1 \le k \le j}{height_k} \]

重要性质喵。

P3809 P2408

模版

CF1073G

数据结构维护 height 有关的信息

P2463

把所有串连一起,插入特殊字符就可。这个技巧 CSP-S 2025 T3 出现过。

SPOJ PHRASES - Relevant Phrases of Annihilation

和上一题套路一样。注意呀,char 只能存128,如果你用 'z' + i 来存特殊分割符,会溢出,这个 RE 调了很久。

P6095

后缀数组只作为工具出现。但是不要以为比较 \(rk_i\)\(rk_j\) 就是在比较 \(s[i , i + len - 1]\)\(s[j , j + len + 1]\) 的字典序。这个受误导自己想了很久qwq

CF822E

同样后缀数组只作为工具出现。

P2178

考察了上述性质。当然难点是将从小到大断开变为从大到小合并,使更好维护。独立想出来了。

LOJ6198

考试考了这道题。和上题一样的套路,让钦定的 LCP 不断减小,合并相邻的两个后缀。对于合并在一起的所有后缀维护一个 01-trie 来存 \(a_i\)。这样合并的时候就可以快速查询了。为保证复杂度正确,要使用启发式合并。其实我上一题就有想过启发式合并,不过那题合并是 \(O(1)\) 的所以没有用武之地。 非常高兴赛时切了。总复杂度 \(O(n \log n \log V)\)。注意到 01-trie 可以看做动态开点的线段树,把启发式合并改为线段树合并,可以消掉一个 \(\log\)。老师还有可持久化 trie 的单 \(\log\) 做法,但是我没听。

P6640

妙妙题。省选给两个人加试一题也太魔幻了。 我们首先对于每一个 \(s\) 的后缀,求一个最长的在 \(t\) 中出现的前缀。具体做法是,把两个串拼在一起,跑一遍SA。考虑上面的重要性质,按字典序排序后,能与 \(s\) 的后缀 \(i\) 匹配 LCP 成为答案的,一定是它前后各一个 \(t\) 的后缀(这句话好像描述的不是很好qwq,语文不好见谅)。前后扫一遍就能搞定。接下来的二分答案比较巧妙,规避了公共子串的右边界超过 \(r\) 的情况。

AC 自动机

因为先写了 SA 的题目,所以 AC 自动机放在后面写了。

构建完 AC 自动机后,拿着我们的文本串 \(s\) 不断在上面走,假设走完了 \(s_{1,i}\),那么 AC 自动机上的节点 u 代表的串就是 \(s_{1 , i}\) 在所有 \(t\) 中的最长后缀。不断跳 fail 可以得到次小后缀、次次小后缀等等。最终得到所有后缀。

SA 获得的是所有后缀的前缀,而这里我们获得了 \(s\) 的前缀的后缀,也就得到了 \(s\) 的所有子串。

P5357

模版

P3121

考验 AC 自动机本质的题。他的上一题 P4824 考察 kmp 的本质。两题放在一起可以理解 kmp 与 AC 自动机的关系。

P3041

AC 自动机上dp。这样做的本质是我们只关心当前已经选好的串 \(s_{1,i}\) 的后缀与模式串的匹配情况,而 AC 自动机只有 \(n\) 个节点,也就是说 \(n\) 种匹配情况。与 \(3^i\) 相比大大压缩。

P2414

稍微对 AC 自动机有点理解就会发现其实是对于每个 y 的前缀 \(s_i\) 在 AC 自动机上的节点 \(u_i\)(是 trie 树上从根开始的链) ,不断跳 fail,如果能碰到 x 对应的节点 \(v\) 则答案+1。不断跳 fail 启示我们建立 fail 树。然后答案就是 \(v\) 子树内 \(u_i\) 的个数。

然后我们发现这题的输入方式使得有 \(10^5\) 个长为 \(10^5\) 的字符串,但是构建出来的 trie 树节点数只有 \(O(n)\)。这使得不能用启发式合并、线段树合并等方法做(伤心喵,都准备开始写了发现不对劲)。合理做法是,在 trie 上访问一个节点时+1,离开时-1,这样有+1的点就是根开始的链。+1 的点在 fail 树上 +1,然后看 \(v\) 子树内权值和。发现 fail 树上的操作是单点加,求子树和,dfn 序和树状数组即可。闲话:这种树上打标记的 trick 根本想不到啊。最近做的另一道:P7165。

可见代码肉眼可见难写,先咕咕咕。

P10716

虽然是 KMP 的题,但是也要建立 fail 树,所以放在一起了。考试遇到的题,虽然当时已经会了上一题建立 fail 树的方法,但是这个是在一个串上,加上做到最后一题神智不清,所以没有想到。再次印证了 KMP 与 AC 自动机的紧密关系。具体的看这篇题解吧,不想写具体做法了。

最后

后缀自动机将来有缘在学吧,如果我能在 NOIP 2026 后继续学 OI 以备战 WC 和 APIO 的话。补完最后两道题,应该将来就不会专门学字符串了。教练之前告诉我学完字符串会沉迷字符串重工业,还是高看我了。

posted @ 2026-07-29 16:54  Natho_nA  阅读(5)  评论(0)    收藏  举报