【模板】后缀数据结构-SAM-SA

参考文献

练手题

CF547E Mike and Friends。

后缀自动机

概念与符号

  • 自动机,转移
  • 终点/endpos/Right集合
  • parent/link/fail
  • len/极长后缀

正确性

考虑构造aabab的SAM,每一个加入的字符会:

  • 建立一些节点;
  • 在一些节点的Right集合中加入len+1;
  • 改变一些link的关系。

考虑遍历last的link,直到第一个有c转移的p:

  • 若p==-1:则原自动机不存在c的转移边,显然cur的Right集合只有一个元素len+1,它的唯一超集是全集。令st[cur].link = 0即可。
  • 否则,设q=st[p].next[c]:
    • 若st[q].len == st[p].len+1,则可以顺利地在q(以及它的所有link)的Right集合中加入len+1,只需要令st[cur].link = q即可。
    • 否则,若st[q].len > st[p].len+1,则cur不能顺利插入,由于st[p].len的最大性,可知q的极长后缀不为cur的后缀。即Right(q)\subsetneq {trans(x,c)|x\in Right'(p)}。(否则,必然存在一个st[p].len更大的p,使得p的Right为last的Right的超集。)这时,应该建立一个新的节点clone,其Right集合为q的Right与{len+1}的并集,并将其link设为q的link,其转移与q一致。显然,q的link节点q_的len小于st[p].len+1(否则,st[p].next[c]应该是q_)
      此时,一些p的(link树上的)祖先的Right集合可能发生改变,若其经过c转移后的r等于q,则需要在其Right集合中插入len+1,故需使得其转移后的结果变为clone;若r不等于q,则可知r为q的祖先,Right集合中已经有了len+1,不需要改变。
  • 每一次sam_extend()后,若p经过转移c后到了q\ne -1,总有Right(q)={x|x\in Right(p), S_{x+1}=c}。

P3804 【模板】后缀自动机 (SAM)

给定一个只包含小写字母的字符串S,请你求出 S 的所有出现次数不为 1 的子串的出现次数乘上该子串长度的最大值。

解答1

后缀自动机即可。 a.cpp Accepted

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e6+10, maxt = maxn<<1;

struct sam{
	int len, link, endpos;
	int next[26];
	sam() { memset(next, -1, sizeof(next)); }
}st[maxt];

int sz, last;
vector<int> son[maxt];

void sam_init() {
	st[0].len = 0;
	st[0].link = -1;
	last = 0;
	sz++;
}

void sam_extend(int c) {
	int cur = sz++;
	st[cur].len = st[last].len+1;
	st[cur].endpos = 1;
	int p = last;
	while (p != -1 && !~st[p].next[c]) {
		st[p].next[c] = cur;
		p = st[p].link;
	}
	if (p == -1) {
		st[cur].link = 0;
	} else {
		int q = st[p].next[c];
		if (st[q].len == st[p].len+1) {
			st[cur].link = q;
		} else {
			int clone = sz++;
			st[clone].link = st[q].link;
			memcpy(st[clone].next, st[q].next, sizeof(st[clone].next));
			st[clone].len = st[p].len+1;
			while (p != -1 && st[p].next[c] == q) {
				st[p].next[c] = clone;
				p = st[p].link;
			}
			st[q].link = st[cur].link = clone;
		}
	}
	last = cur;
}

char s[maxn];
typedef long long ll;
ll ans;

void dfs(int u) {
	for (auto v: son[u]) {
		dfs(v);
		st[u].endpos += st[v].endpos;
	}
	if (st[u].endpos >= 2) {
		ans = max(ans, (ll)st[u].len*st[u].endpos);
	}
}

int main() {
	scanf("%s", s);
	int len = strlen(s);
	sam_init();
	for (int i = 0; i < len; ++i) {
		sam_extend(s[i]-'a');
	}
	for (int i = 1; i < sz; ++i) {
		son[st[i].link].push_back(i);
	}
	dfs(0);
	printf("%lld\n", ans);
}

P3809 【模板】后缀排序 (SA)

a.cpp Accepted

约定

  • sa[i]表示排名为i的后缀的位置;
  • rak[i]表示i后缀的排名;
  • sec[i]表示第二关键字的大小,意义与sa一样;
  • t为桶排使用的数组。

桶排

  • 注意最后一个循环中的数组嵌套的意义!

    int t[maxn];
    void sort() {
    memset(t, 0, sizeof(t));
    for (int i = 1; i <= n; ++i) t[rak[i]]++;
    for (int i = 1; i <= m; ++i) t[i] += t[i-1];
    for (int i = n; i >= 1; --i) sa[t[rak[sec[i]]]--] = sec[i];
    }

代码

#include <cstdio>
#include <cstring>
#define cmp(i, j, w) (sec[i] == sec[j] && sec[i+w] == sec[j+w])
const int maxn = 1e6+10;
int n, sa[maxn], rak[maxn], sec[maxn], m = 200;
char s[maxn];
namespace my{
    int t[maxn];
    void sort() {
        memset(t, 0, sizeof(t));
        for (int i = 1; i <= n; ++i) t[rak[i]]++;
        for (int i = 1; i <= m; ++i) t[i] += t[i-1];
        for (int i = n; i >= 1; --i) sa[t[rak[sec[i]]]--] = sec[i];
    }
}
using my::sort;
int main() {
    scanf("%s", s+1);
    n = strlen(s+1);
    for (int i = 1; i <= n; ++i) {
        rak[i] = s[i];
        sec[i] = i;
    }
    sort();
    for (int w = 1, p = 1; p < n; w<<=1, m=p) {
        for (p = 1; p <= w; ++p) sec[p] = n-w+p;
        for (int i = 1; i <= n; ++i) if (sa[i] > w) sec[p++] = sa[i]-w;
        sort();
        memcpy(sec, rak, sizeof(sec));
        rak[sa[1]] = p = 1;
        for (int i = 2; i <= n; ++i) {
            rak[sa[i]] = (cmp(sa[i], sa[i-1], w) ? p : ++p);
        }
    }
    for (int i = 1; i <= n; ++i) {
        printf("%d ", sa[i]);
    }
}
posted @ 2021-05-20 20:32  frank3215  阅读(108)  评论(0编辑  收藏  举报