后缀自动机练题小记
题意 : 给定一个字符串, 对于每个位置求出经过这个位置的且出现次数为1的子串的长度最小值。
先建出SAM, 出现次数为1相当于它的 \(siz\) 为 1, 考虑如何贡献答案。
对于 \(p = [pos[p] - len[link[p]], pos[p]]\), \(ans[i] = \min(len[link[p]]+1)\)
对于 \(p = [pos[p] - len[p] + 1, pos[p] - len[link[p]]] - 1]\), \(ans[i] = \min(pos[p] - i+1) = \min(pos[p]) - i+1\)。
于是开两颗线段树维护区间取 \(\min\) 操作即可。
然而我最开始是这么写的 :
if(l == r) {ans[p] = std :: min(ans[p], Mn[p] - flag * l); return ;}
噔 噔 咚
使用基数排序代替建树(也许常数小一点) :
for(int i = 1; i <= tot; ++i) c[len[i]] ++;
for(int i = 1; i <= n; ++i) c[i] += c[i - 1];
for(int i = 1; i <= tot; ++i) radix[c[len[i]] --] = i;
for(int i = tot; i >= 1; --i) siz[fa[radix[i]]] += siz[radix[i]];
原理就是对于两个点 \(p, q\), 若 \(p\) 是 \(q\) 的 \(parent\), 则 \(len_p < len_q\), 则在排序中 \(q\) 一定在 \(p\) 之前被访问到, 但有一点需要注意的是, 如果你是用每次把 \(lst\) 初始化为 \(1\) 来建广义后缀自动机,这样写是错误的。
题意 : 给定一个字符串, 定义两个后缀 \(p, q\) 是 \(r\) 匹配的当且仅当 \(r \leq lcp(p, q)\), 权值为 \(a_x \times a_y\), 对于 \(r \in [0, n - 1]\) 求出 \(r\) 匹配的数量和最大权值。
一个很 \(naive\) 的想法就是对于两个后缀只求出它们最长的公共前缀, 然后就是一个后缀加, 后缀取 \(\max\) 的操作, 因为两个前缀的最长公共后缀就等于它们所对应的 \(parent\) 树上的 \(LCA\), 发现两个东西完全相反, 于是把串反过来, 做一次 \(DP\) 即可。
关键代码 :
inline bool pt(int x) {
return Mn[x] != INF && Mx[x] != -INF;
}
inline void dfs(int x, int fa) {
for(int i = 0; i < P[x].size(); ++i) {
int y = P[x][i]; dfs(y, x);
ans1[len[x]] += 1ll * siz[x] * siz[y], siz[x] += siz[y];
if(pt(x) && pt(y)) ans2[len[x]] = std :: max(std :: max(1ll * Mx[x] * Mx[y], 1ll * Mn[x] * Mn[y]), ans2[len[x]]);
Mx[x] = std :: max(Mx[x], Mx[y]), Mn[x] = std :: min(Mn[x], Mn[y]);
}
}
题意 : 求多个串的最长公共字串。
考虑只有两个串怎么做 : 先对一个串建后缀自动机, 然后类似\(AC\)自动机, 对于另一串的每一位, 若当前节点有此字符, 就直接转移, 否则跳 \(fa\), 因为一个节点的 \(fa\) 一定是它的后缀, 于是跳 \(fa\) 可以感性理解为从长到短枚举它的后缀。
对于多串, 也先对一个串建自动机, 枚举其它串。
对于每一个状态维护一个 \(mx\) 表示当前串与原串在此状态的 \(LCS\),在对于每个状态求出 \(mx\) 的最小值, 这是它和所有串的 \(LCS\), 答案就是所有 \(mn\) 的 \(\max\)。
但是因为一个状态能够匹配, 它的所有父亲都可以匹配, 于是 \(mx_p\) 还要对 \(mx_{ch}\) 取 \(\max\)。
inline void dfs(int x, int fa) {
for(int i = 0; i < P[x].size(); ++i) {
int y = P[x][i];
dfs(y, x), mx[x] = std :: max(mx[x], std :: min(len[x], mx[y]));
}
}
也是多串 \(LCS\), 只不过字符串可顺序可逆序。
这还不简单, 把每个串顺序和逆序都存在里面, 在中间插入一个分隔符即可。
题意 : 令 \(T_i\) 表示从第 \(i\) 位开始的后缀, 求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n} len(T_i) + len(T_j) - 2 \times len(lcp(T_i, T_j))\).
把原串翻转后建出后缀自动机, 然后就和【NOI2015】品酒大会 一样了。
题意 : 给定两个字符串 \(s, t\), 每次询问 \(l, r\), 求出 \(s[l \dots r]\) 和 \(t\) 的最长公共子串。
对 \(t\) 建立后缀自动机, 对于 \(i \in [1, n]\), 求出以 \(i\) 结尾的最长公共子串, 记为 \(p[i]\)。
容易发现每次所能匹配到的左端点 \(i - p[i] + 1\), 是单调不降的, 于是可以二分一个位置 \(pos \in [l, r]\), 在 \(pos\) 的左边的最长公共字串 \(< l\), 在右边都 \(\geq l\), 于是右边的答案贡献就是 \(\max{p[i]} (i \in [pos, r])\), 左边的贡献为 \(pos - 1 - l + 1 = pos - l\), 对右边用 $st$表或线段树维护, 最后取个 \(\max\) 即可。