3 条题解
-
0
广义 SAM 板子题。
思路
首先对所有串建广义 SAM。
本题和模版唯一不同的一点就是,模版是统计总共有多少子串,多个相同串虽然不算,但是起码算一个,而这题是一个都不算。。。
考虑在 parent 树上染色。
做法:
-
建广义 SAM。
-
对于每个字符串,往上跳 father 指针。
如果 parent 树当前节点已经染过其他颜色了,因为多个相同串一个都不算,那么就把它设为不可用。
否则,将该节点染色为当前字符串编号。
最后统计答案的时候,直接按照染上的颜色统计即可。
如果当前颜色不可用,那就不管。否则把对应颜色的答案加上子串数量。
如果你不知道怎么求子串数量,
说明你根本不会用 SAM,请参考 P2408 和 P6139。还是很简单的。
具体实现(
update即染色函数):inline void update(int x, int id) { while (~x && ~num[x] && num[x] != id) { num[x] = num[x] ? -1 : id; x = fa[x]; } } inline void solve() { int p = 0; for (int i = 1; i <= n; i++, p = 0) for (char c : s[i]) update(p = t[p][c - 'a'], i); for (int i = 0; i <= tot; i++) if (~num[i] && ~fa[i]) ans[num[i]] += len[i] - len[fa[i]]; for (int i = 1; i <= n; i++) cout << ans[i] << '\n'; }代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 5; string s[N]; int n; struct SAM { int tot, last, fa[N << 1], len[N << 1], t[N << 1][26]; int num[N << 1], ans[N << 1]; inline SAM() { tot = last = 0; fa[0] = -1; } inline void insert(char c) { c -= 'a'; int cur = ++tot; len[cur] = len[last] + 1; int p = last; last = cur; while (~p && !t[p][c]) t[p][c] = cur, p = fa[p]; if (!~p) { fa[cur] = 0; return; } int x = t[p][c]; if (len[p] + 1 == len[x]) fa[cur] = x; else { len[++tot] = len[p] + 1; fa[tot] = fa[x]; memcpy(t[tot], t[x], sizeof(t[x])); while (~p && t[p][c] == x) t[p][c] = tot, p = fa[p]; fa[x] = fa[cur] = tot; } } inline void update(int x, int id) { while (~x && ~num[x] && num[x] != id) { num[x] = num[x] ? -1 : id; x = fa[x]; } } inline void solve() { int p = 0; for (int i = 1; i <= n; i++, p = 0) for (char c : s[i]) update(p = t[p][c - 'a'], i); for (int i = 0; i <= tot; i++) if (~num[i] && ~fa[i]) ans[num[i]] += len[i] - len[fa[i]]; for (int i = 1; i <= n; i++) cout << ans[i] << '\n'; } } sam; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n; for (int i = 1; i <= n; i++) { cin >> s[i]; sam.last = 0; for (char c : s[i]) sam.insert(c); } sam.solve(); return 0; } -
-
0
后缀数组 SA 做法题解 SA 做法代码:
#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; }后缀自动机 SAM 做法题解 SAM 做法代码:
#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; } -
0
SA 做法代码:
#include<bits/stdc++.h> using namespace std;
后缀自动机 SAM 做法题解</p>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;}
SAM 代码:
#include<bits/stdc++.h> using namespace std;
</p>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;}
- 1
信息
- ID
- 6830
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 9
- 已通过
- 2
- 上传者