Parent Tree 上面的边不是 SAM 的边,所以 Parent Tree作为一种辅助构建 SAM 的结构。
SAM,启动!
首先要明确,SAM 是使用增量构造法构造(就是一个字符一个字符往上加)。
还有一点是,SAM 构造时不需要求出 endpos (给一张老师 PPT 上的图片)
好了,开始构造
对于一个状态,我们存3个东西:
1 2 3 4 5
structnode { int len, link; int ch[26]; } sam[N];
其中 len 与 link 代表上文的 maxlen 与 link,ch 储存转移
我们记上一个插入的状态为 last, 插入前的串为 s,插入的为 c.
接着是插入结点的过程:
新建一个编号为 cur 的状态
为他的 len 赋值为 len(last)+1.因为 len 为最长的长度,则为 ∣s∣+1,也就是 len(last)+1
顺着 last 一直往 link(last) 跳,记当前的点为 p。因为这显然是 s 的后缀,所以当没有 c 的转移时,可以直接为 p 连接一条向 cur 的为 c 的转移。
直到跳到 link(1) 或者 p 出现了向 c 的转移,停止上一步,分以下三种情况:
p=link(1),也就表明不会出现包含 cur 的等价类,则 link(cur) 指向 1
记 q 为 p 向 c 转移到的节点,若 len(p)+1=len(q),则 link(cur) 指向 q,因为此处 q 处的最长子串是由 p 处增加字符得来,那么 q 处的字符串就显然是 cur 处的后缀(因为都是由 Parent Tree 上 last 到根这一条链中的状态向 c 转移),且一定是符合此条件中 len 最大的一个。
结合图片吧……
若len(p)+1>len(q),则需要将 q 的状态分割成上一种情况的和由其他状态转移的,那么将 p 复制到状态 clone,len(clone) 设为 len(p)+1,然后将 link(q)、link(cur) 全部指向 clone,将Parent Tree 上 p 到根这一条链中 c 的转移转移到 q 的改为转移到 clone.
int last = 1, idx = 1; structnode { int len, link; int ch[26]; } sam[N]; voidinsert(char ch) { int c = ch - 'a'; int p = last, cur = ++ idx; sam[cur].len = sam[p].len + 1, last = cur; for(; p && !sam[p].ch[c]; p = sam[p].link) sam[p].ch[c] = cur; if(!p) sam[cur].link = 1; else { int q = sam[p].ch[c]; if(sam[q].len == sam[p].len + 1) sam[cur].link = q; else { int clone = ++ idx; sam[clone] = sam[q]; sam[clone].len = sam[p].len + 1; sam[q].link = sam[cur].link = clone; for(; p && sam[p].ch[c] == q; p = sam[p].link) sam[p].ch[c] = clone; } } }
应用
求解 endpos 大小
为每一个插入的 cur 节点赋上 1, endpos 大小即为子树之和。
1 2 3 4 5 6
//... int cur = ++ idx, p = last; siz[cur] = 1; //... vector<int> e[N]; voidbuild(){for(int i = 2; i <= idx; i ++) e[sam[i].link].push_back(i);} voiddfs(int u){for(auto v : e[u]) dfs(v), siz[u] += siz[v];}
对其中一个建立 SAM,将另外一个从根节点开始匹配,失败时跳 link 直到继续匹配。过程中的最大匹配长度即为答案。
1 2 3 4 5 6 7 8 9 10 11 12 13
int p = 1, len = 0, ans = 0; for(int i = 1; i <= m; i ++) { int c = t[i] - 'a'; if(sam[p].ch[c]) len ++, p = sam[p].ch[c]; else { for(; p && !sam[p].ch[c]; p = sam[p].link) ; if(p) len = sam[p].len + 1, p = sam[p].ch[c]; else p = 1, len = 0; } ans = max(ans, len); }
多串
对其中一个建立 SAM,将另外的依次匹配。在串内,统计每个点上最大值(由于有多串,计算时要同时更新 link 的值。串外再取最小值,最后每个节点最小值的最大值即为答案。
while(cin >> t + 1) { int m = strlen(t + 1); int p = 1, len = 0; for(int i = 1; i <= m; i ++) { int c = t[i] - 'a'; if(sam[p].ch[c]) len ++, p = sam[p].ch[c]; else { for(; p && !sam[p].ch[c]; p = sam[p].link) ; if(p) len = sam[p].len + 1, p = sam[p].ch[c]; else p = 1, len = 0; } _l[p] = max(_l[p], len); int f = sam[p].link; _l[f] = max(_l[f], min(_l[p], sam[f].len)); } for(int i = 1; i <= idx; i ++) l[i] = min(_l[i], l[i]); memset(_l, 0, sizeof _l); } int ans = 0; for(int i = 1; i <= idx; i ++) ans = max(ans, l[i]);