【题解】洛谷 P4081 [USACO17DEC] Standing Out from the Herd P [后缀数组 SA]
没学过指路:【详细注释 | 字符串算法集合 2】后缀自动机 SAM & 后缀数组 SA-CSDN博客
P4081 [USACO17DEC] Standing Out from the Herd P - 洛谷 (luogu.com.cn)
后缀数组 SA 做法
整出 height 数组后枚举字典序排名相邻的两个后缀,通过 LCP 统计答案。
至于判断哪个后缀是哪个字符串,看 sa 数组(后缀的起始位置)就行。
具体看我代码注释,太阴了这做法。
注意!!!如果你大数据输出为负数,请检查你的 s 数组是否为 int 类型。
最好是传入 s[i] - 'a',间隔符是否全部都不相同(防止不存在的公共子串)
代码:
#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 4e5 + 10;char ss[N];
int n, m;
LL ed[N];
int c[N], x[N], y[N], sa[N];
int s[N]; // 这里一定要用 int !! void get_sa() {int i, j, k;memset(c, 0, sizeof(c));for (i = 1; i <= n; i ++) {c[x[i] = s[i]] ++;}for (i = 1; i <= m; i ++) {c[i] += c[i - 1];}for (i = n; i >= 1; i --) {sa[c[x[i]]] = i;c[x[i]] --;}for (k = 1; k <= n; k <<= 1) {for (i = 1; i <= m; i ++) {c[i] = 0;}for (i = 1; i <= n; i ++) {y[i] = sa[i];}for (i = 1; i <= n; i ++) {c[x[y[i] + k]] ++;}for (i = 1; i <= m; i ++) {c[i] += c[i - 1];}for (i = n; i >= 1; i --) {sa[c[x[y[i] + k]]] = y[i];c[x[y[i] + k]] --;}for (i = 1; i <= m; i ++) {c[i] = 0;}for (i = 1; i <= n; i ++) {y[i] = sa[i];}for (i = 1; i <= n; i ++) {c[x[y[i]]] ++;}for (i = 1; i <= m; i ++) {c[i] += c[i - 1];}for (i = n; i >= 1; i --) {sa[c[x[y[i]]]] = y[i];c[x[y[i]]] --;}for (i = 1; i <= n; i ++) {y[i] = x[i];}for (m = 0, i = 1; i <= n; i ++) {if (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) {x[sa[i]] = m;}else {m ++;x[sa[i]] = m;}}if (m == n) {break;}}
}int height[N], rk[N];
int mp[N];
LL ans[N];void get_height() {int i, j, k;memset(height, 0, sizeof(height));for (i = 1; i <= n; i ++) {rk[sa[i]] = i;}for (k = 0, i = 1; i <= n; i ++) {if (rk[i] == 1) {continue;}if (k) {k --;}int j = sa[rk[i] - 1];while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) {k ++;}height[rk[i]] = k;}
}int main () {ios::sync_with_stdio(false);cin.tie(0);int T;cin >> T;n = 0; for (int i = 1; i <= T; i ++) {cin >> ss + 1;for (int j = 1; ss[j]; j ++) {n ++;s[n] = ss[j] - 'a'; // 这里一定要 - 'a'!!!不然会爆 long long mp[n] = i;}ed[i] = n;n ++;m = s[n] = 26 + i; // 间隔符:别管是啥妖魔鬼怪不一样就行,同时更新上限 m // 只要字典序不插在中间干扰答案就行 }get_sa();get_height();LL mn = 0;// mn:从上一个不同字符串的字典序相邻后缀到当前后缀的 LCP 最小值 memset(ans, 0, sizeof(ans));for (int i = 1; i <= n; i ++) {LL L = height[i];mn = min(L, mn); int mx = mp[sa[i]], my = mp[sa[i - 1]];if (mx == my) { // 相邻俩后缀属于一个字符串 ans[mx] += ed[mx] - sa[i] + 1 - L;// 字符串 mx 加上自己以 sa[i] 为开头的剩下的不重复子串// 比如说从 sa[i] 到字符串结尾还剩 abc,height 为 2// 那么从 a 开始有 3(字符串剩下长度)个后缀:a,ab,abc// 再减掉 2(a 和 ab),就是不重复子串 }else {ans[mx] += ed[mx] - sa[i] + 1 - L;// 和前面那个一样,都是 mx 自身的答案贡献 ans[my] += mn - L;/* *** 难点假设上一个不同字符串到当前的子串分别是:ba,bb,bbb,bbbb,bbbc除了中间三个全是 b 的所属同一个字符串 s,其他头尾两个都是不同的 mn = 1,字符串 s 的答案贡献在 i = bb 时减了 b 在 i = bbb 减了 b 和 bb 在 i = bbbb 时减了 b 和 bb 和 bbb在 i = bbbc 时减了 b 和 bb 和 bbb我们发现同一个 b 的贡献在和别的字符串匹配时减了 2 次但实际上 bbbb 字符串减 b 的贡献只用减 1 次(本来要减 4 次,但剩下的 b 在 mx = my 时把自身重复的都减掉了) (我们这里只讨论 bbbb 在减掉自身相重复的子串后,与外界相重复的子串应该怎么减)而 b 这个子串与其它两个字符串的后缀相重复,在 bbbb 内的贡献减 1 次就好所以还要将 mn 加回来 */mn = L; // 更新 mn }}for (int i = 1; i <= T; i ++) {cout << ans[i] << "\n";}return 0;
}