洛谷 P6139 【模板】广义后缀自动机(广义 SAM) 题解
一、题目:
二、思路:
这是一道模板题。网上的大部分写法太假了,所以就认真学了一下真的广义后缀自动机。其实还挺好懂的。
在线版
大概思路就是说每次遇到一个新串,就把las赋成1。但是这还没完,如果这样做的话,肯定会遇到\(node[las].ch[s]\)已经存在的情况。这时要分类讨论。
设\(v\)是\(las\),\(x\)为\(node[v].ch[s]\)。
- \(node[v].len+ 1 = node[x].len\)。说明当前子串已经完全处于自动机之中,我们直接将\(las\)赋成\(x\),便于下一次继续。
- \(node[v].len + 1 <node[x].len\)。我们需要从\(x\)拆分出一个点\(y\),类似普通的后缀自动机进行处理即可。
离线版
先把这堆串的trie树建出来。
发现trie的每一个节点代表一个不同前缀。所以只需要把这些不同的前缀插入到SAM中即可。
使用BFS即可解决。
三、代码:
// 在线版
#include <iostream>
#include <cstdio>
#include <cstring>
#define mem(s, v) memset(s, v, sizeof s)
using namespace std;
const int maxn = 1e6 + 5;
int n, las = 1, tot = 1;
char ch[maxn];
struct Node {
int fa, len;
int ch[27];
}node[maxn << 1];
inline int extend(int s) {
int v = las;
if (node[v].ch[s]) {
int x = node[v].ch[s];
if (node[v].len + 1 == node[x].len) return x;
int y = ++ tot;
node[y] = node[x];
node[y].len = node[v].len + 1;
node[x].fa = y;
for (; v && node[v].ch[s] == x; v = node[v].fa) node[v].ch[s] = y;
return y;
}
int z = ++ tot;
node[z].len = node[v].len + 1;
for (; v && node[v].ch[s] == 0; v = node[v].fa) node[v].ch[s] = z;
if (!v) { node[z].fa = 1; return z; }
int x = node[v].ch[s];
if (node[x].len == node[v].len + 1) { node[z].fa = x; return z; }
int y = ++ tot;
node[y] = node[x];
node[y].len = node[v].len + 1;
node[z].fa = node[x].fa = y;
for (; v && node[v].ch[s] == x; v = node[v].fa) node[v].ch[s] = y;
return z;
}
int main() {
int t; scanf("%d", &t);
while (t --) {
scanf("%s", ch + 1);
n = strlen(ch + 1);
las = 1;
for (int i = 1; i <= n; ++ i) las = extend(ch[i] - 'a' + 1);
}
long long ans = 0;
for (int i = 2; i <= tot; ++ i) {
(ans += node[i].len - node[node[i].fa].len);
}
printf("%lld\n", ans);
return 0;
}
// 离线版
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 1e6 + 5;
char ch[maxn];
int trie[maxn][27], sz = 1, fa[maxn], c[maxn];
int pos[maxn], tot = 1;
struct Node {
int fa, len;
int ch[27];
}node[maxn << 1];
inline void insert(char *str, int n) {
int p = 1;
for (int i = 1; i <= n; ++ i) {
if (!trie[p][str[i] - 'a' + 1]) trie[p][str[i] - 'a' + 1] = ++ sz, fa[sz] = p;
p = trie[p][str[i] - 'a' + 1];
c[p] = str[i] - 'a' + 1;
}
}
inline int extend(int s, int las) {
int z = ++ tot, v = las;
node[z].len = node[v].len + 1;
for (; v && node[v].ch[s] == 0; v = node[v].fa) node[v].ch[s] = z;
if (!v) node[z].fa = 1;
else {
int x = node[v].ch[s];
if (node[x].len == node[v].len + 1) node[z].fa = x;
else {
int y = ++ tot;
node[y] = node[x];
node[y].len = node[v].len + 1;
node[x].fa = node[z].fa = y;
for (; v && node[v].ch[s] == x; v = node[v].fa) node[v].ch[s] = y;
}
}
return z;
}
inline void build(void) {
queue<int>q;
for (int i = 1; i <= 26; ++ i)
if (trie[1][i])
q.push(trie[1][i]);
pos[1] = 1; // pos代表trie树上的节点在SAM中的编号
while (q.size()) {
int x = q.front(); q.pop();
pos[x] = extend(c[x], pos[fa[x]]);
for (int i = 1; i <= 26; ++ i)
if (trie[x][i])
q.push(trie[x][i]);
}
}
int main() {
int t; cin >> t;
while (t --) {
scanf("%s", ch + 1);
insert(ch, strlen(ch + 1));
}
build();
long long ans = 0;
for (int i = 2; i <= tot; ++ i) {
(ans += node[i].len - node[node[i].fa].len);
}
cout << ans << endl;
return 0;
}