【题解】洛谷 P4081 [USACO17DEC] Standing Out from the Herd P [后缀自动机 SAM]
没学过指路:【详细注释 | 字符串算法集合 2】后缀自动机 SAM & 后缀数组 SA-CSDN博客
P4081 [USACO17DEC] Standing Out from the Herd P - 洛谷 (luogu.com.cn)
广义后缀自动机 SAM 解法
和普通 SAM 的唯一区别就是每个串开始前 np = 1,
不在上一个字串的后面接,就和 AC 自动机 p = 0 差不多。
np = 1 其实 ch 数组和 fa 数组没什么大的区别,不同字符串同样的后缀照样可以链接。
(这里可以去看洛谷上第一篇题解的图片,上一个字符串的字符通过根节点照样可以走到)
跑完 SAM 后,遍历每个串下标节点的 fa 一条链。
(为什么是下标节点?因为只有下标节点的结束位置才不相同,才是真正的互不相同的后缀)
定义 v 数组初始化为 0,v[i] 代表节点 i 属于哪个串,为 -1 时代表节点 i 同时属于很多串。
如果这个节点的 v = 0 则 v = 当前字符串编号,反之 v 已经有了别的就 v[i] = -1。
(遍历整条 fa 链,有可能到不同字符串的节点,这些节点代表相同的后缀,也就是重复子串)
最后再次遍历所有节点,统计答案 ans[当前字符串编号] = len[i] - len[fa[i]],
因为 fa 指向的是和 i 有最长公共后缀的且不同结束位置节点,
两者 len 的差正好就是以 i 为结尾但从没出现过的子串。
还有些细节写代码注释里了:
#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 2e5 + 10;char s[N], ss[N];
int np, tot;
int len[N], v[N], slen[N];
int ch[N][30], fa[N];
LL ans[N];void extend(int c) {int p = np;tot ++;np = tot;len[np] = len[p] + 1;for (; p && !ch[p][c]; p = fa[p]) {ch[p][c] = np;}if (!p) {fa[np] = 1;}else {int r = ch[p][c];if (len[r] == len[p] + 1) {fa[np] = r;}else {tot ++;int nr = tot;len[nr] = len[p] + 1;fa[nr] = fa[r];fa[r] = nr;fa[np] = nr;for (; p && ch[p][c] == r; p = fa[p]) {ch[p][c] = nr;} memcpy(ch[nr], ch[r], sizeof(ch[r]));}}
} void get_v(int x, int i) {for (; x && v[x] != i && v[x] != -1; x = fa[x]) { // 遇到 v[x] 不是当前字符串,那么 x 的后缀也不是当前字符串,直接退出就好 if (v[x] != 0) {v[x] = -1;}else {v[x] = i;}}
}int main () {ios::sync_with_stdio(false);cin.tie(0);tot = np = 1;fa[1] = 0;memset(len, 0, sizeof(len));memset(v, 0, sizeof(v));memset(ch, 0, sizeof(ch));int n, sl = 0;cin >> n;for (int i = 1; i <= n; i ++) {np = 1;cin >> ss + 1;slen[i] = strlen(ss + 1);for (int j = 1; ss[j]; j ++) { // 神人出题人,搞得读入像坨 s[sl + j] = ss[j];extend(ss[j] - 'a');}sl += slen[i];}int x = 1, last = 0;for (int i = 1; i <= n; i ++) {x = 1;for (int j = 1; j <= slen[i]; j ++) {x = ch[x][s[last + j] - 'a'];get_v(x, i);}last += slen[i];}memset(ans, 0, sizeof(ans));for (int i = 1; i <= tot; i ++) if (v[i] != -1) {ans[v[i]] += (len[i] - len[fa[i]]);}for (int i = 1; i <= n; i ++) {cout << ans[i] << "\n";}return 0;
}