垂死病中惊坐起, 笑闻学弟切SAM
是时候补一篇学习笔记了
为啥每次我打后缀自动机都出来孩子战斗机啊
定义
// 基本上是自己瞎捷豹编的
s
: SAM的起点, 一切转移从这里开始...
endpos[(string) x]
:
字符串x
在原串中出现位置(取x
结尾的下标)的集合
终点:
所有endpos
包含n
的节点都是终点(感谢yt姐姐告诉我终点有很多个qwq)
节点: 自动机中的点, 一个节点包含着一类endpos
相同(必须 完
全 一 致 )的子串
maxlen[x]
和minlen[x]
:
代表节点x
中包含的长度最长的子串和长度最短的子串(有的时候也省略为起长度)
节点后缀: 对于节点x
,
节点后缀就是maxlen[x]
的所有后缀,
显然这个节点包含的所有的子串属于这个点的节点后缀,
而长度大于minlen[x]
的节点后缀就是这个节点所包含的节点
边: - trans[x][y]
:
在节点x
处后一个字符为y
时转移到的节点;
如果不能识别, trans[x][y] = NULL
-
suff_link[x]
: 指向 [包含
[第一个长度小于minlen[x]
的节点后缀] 的节点],
因为minlen
沿着suff_link
是单调递减的,
所以最后一定会变为空串, 即指向初始节点s
-
suff_path[x]
: 从节点x
开始,
由suff_link
一直向上, 组成的路径,
终点为s
(显然suff_path[x]
上的节点的endpos
都会包含endpos[x]
)
转移
// 一个大分类讨论...
maxlen
为当前串的节点为x
,
新加入的字符为y
...我们现在需要给所有合法的后缀都在后面加上一个字符y
,
因为原串扩充了一位所以肯定要增加一个节点, 先++cntn
为敬
I
对于任意节点\(j \in
\text{suff_path[x]}\), 都有trans[j][y] = NULL
也就是所有的终点(suff_path[x]
上所有点的endpos
都包含n
,
也就是说他们都是终点)即当前串的所有后缀在当前串的子串都不能向y
转移,
既然坑上没有萝卜我们就可以直接让suff_path
上的节点trans[j][y] = cntn
;
最后让suff_link[cntn] = s
,
因为之前的串上没有trans[j][y]
, 终点又变成1个了
II
在沿着suff_path[x]
向上跳的时候,
对于trans[j][y] = NULL
的点,
同I我们可以直接把他们的trans
赋成cntn
;
而遇到trans[j][y] != NULL
且maxlen[trans[j][y]] = maxlen[j] + 1
(为什么放在下面说)的情况时,
我们发现我们没有必要继续向上跳了(上面的点也一定有trans[j][y] != NULL
)我们可以直接把这些trans[j][y]
作为终点,
让suff_link[x] = trans[j][y]
然后就可以直接返回了
III
在沿着suff_path[x]
向上跳的时候,
trans[j][y] = NULL
时处理还是同上
否则trans[j][y] != NULL
且maxlen[trans[j][y]] > maxlen[j] + 1
:
这个trans[j][y]
并不是直接由j
转移来的,
所以如果我们把这个点当做一个终点的话,
就会造成非后缀被识别为后缀(因为到达了终点), 所以我们要复制一个新点;
我们新建一个点c
,
suff_link[c] = suff_link[trans[j][y]], trans[c] = trans[trans[j][y]], maxlen[c] = maxlen[j] + 1
,
即我们新建一个节点作为终点, 且是只由j
转移来的,
然后后面的就和II是一样的了:
suff_link[cntn] = c
然后再做一些处理:
修改suff_link[trans[j][y]] = c
(节点c
与trans[j][y]
的最长公共后缀是最长的);
沿着suff_path[j]
向上走,
对于路径上所有通过y
转移到trans[j][y]
的节点g
:
trans[g][y] = c
(这些节点的后缀都是j
的后缀,
加上y
后也就是新串的后缀);
是不是也不是很难?
几道例题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #define LL long long using namespace std; const int MAXZ = 26, MAXN = 1000000 + 5; struct SAM { struct node { int trans[MAXZ], suff_link, maxlen; node (int ml = 0): suff_link(0), maxlen(ml) { memset(trans, 0, sizeof(trans)); } } b[MAXN * 2]; int s, last, cntb; void init() { s = last = cntb = 1; } int ins(char xc) { int y = xc - 'a', dq = ++cntb, dql = last; b[dq] = node(b[dql].maxlen + 1); for (; dql && !b[dql].trans[y]; dql = b[dql].suff_link) b[dql].trans[y] = dq; if (!dql) b[dq].suff_link = s; else if (b[b[dql].trans[y]].maxlen == b[dql].maxlen + 1) b[dq].suff_link = b[dql].trans[y]; else { int c = ++cntb, bd = b[dql].trans[y]; b[c] = node(b[dql].maxlen + 1); for (int i = 0; i < MAXZ; i++) b[c].trans[i] = b[bd].trans[i]; b[c].suff_link = b[bd].suff_link; b[bd].suff_link = b[dq].suff_link = c; for (; dql && b[dql].trans[y] == bd; dql = b[dql].suff_link) b[dql].trans[y] = c; } return (last = dq); } }refun; int main() { refun.init(); char srs[MAXN]; scanf("%s", srs); int n = strlen(srs); LL ans = 0; for (int i = 0; i < n; i++) { int last = refun.ins(srs[i]); ans += (refun.b[last].maxlen - refun.b[refun.b[last].suff_link].maxlen); } printf("%lld", ans); return 0; }
|
这题还是挺有意思的
endpos[x]
就是所有suff_link[j] == x
(这些j
被称为\(parent\)树上x
的儿子)的endpos
的并(看不懂的去瞅一眼定义...);
如果这个x
包含了总串的某个前缀,
这个endpos[x]
还要再加上自身的位置(这个位置肯定不会包含在x
的儿子中,
因为儿子都比x
要长, 不会是原串这个位置的前缀)
然后数组开小了加上手残调了一个多小时...
我不知道我都干了什么...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #define LL long long using namespace std; const int MAXZ = 26, MAXN = 1000000 + 5; struct SAM { struct node { int maxlen, suff_link, trans[MAXZ]; node(int maxlen = 0): maxlen(maxlen), suff_link(0) { memset(trans, 0, sizeof(trans)); } } b[MAXN << 1]; int s, last, cntb; void init() { s = last = cntb = 1; } int ins(char xc) { int dq = ++cntb, y = xc - 'a', dql = last; b[dq] = node(b[last].maxlen + 1); for (; dql && !b[dql].trans[y]; dql = b[dql].suff_link) b[dql].trans[y] = dq; if (!dql) b[dq].suff_link = s; else if (b[dql].maxlen + 1 == b[b[dql].trans[y]].maxlen) b[dq].suff_link = b[dql].trans[y]; else { int c = ++cntb, dqd = b[dql].trans[y]; b[c] = node(b[dql].maxlen + 1); b[c].suff_link = b[dqd].suff_link; b[dqd].suff_link = b[dq].suff_link = c; for (int i = 0; i < MAXZ; i++) b[c].trans[i] = b[dqd].trans[i]; for (; dql && b[dql].trans[y] == dqd; dql = b[dql].suff_link) b[dql].trans[y] = c; } return (last = dq); } }refun; struct edg { int to, next; } b[MAXN << 1]; int g[MAXN << 1], cntb, size[MAXN << 1], ans[MAXN]; void adn(int from, int to) { b[++cntb].next = g[from]; b[cntb].to = to; g[from] = cntb; } int dfs(int dq) { for (int i = g[dq]; i; i = b[i].next) size[dq] += dfs(b[i].to); ans[refun.b[dq].maxlen] = max(ans[refun.b[dq].maxlen], size[dq]); return size[dq]; } int main() { refun.init(); int n = 0; do { char src = getchar(); if (src < 'a' || src > 'z') break; size[refun.ins(src)] = 1, ++n; } while (true); for (int i = 2; i <= refun.cntb; i++) adn(refun.b[i].suff_link, i); dfs(1); for (int i = n - 1; i >= 1; i--) ans[i] = max(ans[i], ans[i + 1]); for (int i = 1; i <= n; i++) printf("%d\n", ans[i]); return 0; }
|
这种有多个串的题...如果一个答案只能在一个位置算贡献,
我们就不能对多个串分开来算了, 我们可以把这些串都拼在一起,
然后在中间加上不可见字符作为分割
这样这个题就很简单了...
忘初始化了...身败名裂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <queue> #define MAXN (2000000 + 5) #define MAXZ (11) #define Refun (1000000000 + 7) #define LL long long using namespace std; struct SAM { struct node { int maxlen, suff_link, trans[MAXZ], vsize; LL sum; node(int maxlen = 0): maxlen(maxlen), suff_link(0), sum(0), vsize(0) { memset(trans, 0, sizeof(trans)); } } b[MAXN << 1]; int s, last, cntb, du[MAXN]; bool inq[MAXN << 1]; void init() { s = last = cntb = 1; } void ins(int y) { int dq = ++cntb, dql = last; b[dq] = node(b[dql].maxlen + 1); for (; dql && !b[dql].trans[y]; dql = b[dql].suff_link) b[dql].trans[y] = dq; if (!dql) b[dq].suff_link = s; else if (b[dql].maxlen + 1 == b[b[dql].trans[y]].maxlen) b[dq].suff_link =b[dql].trans[y]; else { int c = ++cntb, dqd = b[dql].trans[y]; b[c] = node(b[dql].maxlen + 1); b[c].suff_link = b[dqd].suff_link; for (int i = 0; i < MAXZ; i++) b[c].trans[i] = b[dqd].trans[i]; b[dqd].suff_link = b[dq].suff_link = c; for (; dql && b[dql].trans[y] == dqd; dql = b[dql].suff_link) b[dql].trans[y] = c; } last = dq; } void solve() { LL ans = 0; for (int i = 1; i <= cntb; i++) for (int j = 0; j < MAXZ; j++) if (b[i].trans[j]) ++du[b[i].trans[j]]; queue<int> q; q.push(1); b[1].vsize = 1; while (!q.empty()) { int dq = q.front(); ans = (ans + b[dq].sum) % Refun; q.pop(); for (int i = 0; i < MAXZ; i++) if (b[dq].trans[i]) { --du[b[dq].trans[i]]; if (i != 10) { b[b[dq].trans[i]].vsize += b[dq].vsize; b[b[dq].trans[i]].sum = (b[b[dq].trans[i]].sum + b[dq].sum * 10 + i * b[dq].vsize) % Refun; } if (!du[b[dq].trans[i]]) q.push(b[dq].trans[i]); } } printf("%lld", ans); } } refun; int n; char s[MAXN]; int main() { scanf("%d", &n); refun.init(); for (int i = 1; i <= n; i++) { scanf("%s", s); int tn = strlen(s); for (int j = 0; j < tn; j++) refun.ins(s[j] - '0'); refun.ins(10); } refun.solve(); return 0; }
|
By 准备退役旅游的 Cansult