知识点 - 后缀自动机
解决问题类型:
- 询问一些模式串P是否出现在文本串T中
- 询问一些模式串P文本串T中出现了几次
- 询问一些模式串P文本串T中第一次出现的位置
- 询问一些模式串P文本串T中所有出现的位置
- S串中有几个不同的子串
- S串中不同子串的总长度
- 求S字典序第K大的子串
- 求S字典序最小的循环位移(cyclic shift)
- 求S最短的没有出现的子串
- 求S1,S2的最长公共子串
- 求 k k k个串 S i S_i Si的公共子串
论文:
2015 后缀自动机再字典树上的拓展
张天扬,《后缀自动机及其应用》,2015年国家集训队论文。
2014 陈立杰 后缀自动机
定义与模板
直观上,某个串的SAM可以理解为其所有子串的高度压缩形式。时空复杂度为 O ( n ) O(n) O(n).
严格的定义是:可以接受串s所有后缀的最小的确定有穷自动机。
i.e.
- SAM是一个有向图,转移是字符。如果我们从初始状态转移到一个终止状态,所有转移会拼出一个后缀。
struct state {
int len, link;
map<char, int> next;
};
const int MAXLEN = 100000;
state st[MAXLEN * 2];
int sz, last;
void sa_init() {
st[0].len = 0;
st[0].link = -1;
sz++;
last = 0;
}
void sa_extend(char c) {
int cur = sz++;
st[cur].len = st[last].len + 1;
int p = last;
while (p != -1 && !st[p].next.count(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[p].len + 1 == st[q].len) {
st[cur].link = q;
} else {
int clone = sz++;
st[clone].len = st[p].len + 1;
st[clone].next = st[q].next;
st[clone].link = st[q].link;
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;
}
复杂度:
O ( n ) O(n) O(n) 时间 O ( n k ) O(n k) O(nk)空间
或 O ( n log k ) O(n \log k) O(nlogk)时间 O ( n ) O(n) O(n)空间
k是字母表,用map存的话会多一个log
例题
exe:
代码
#include<bits/stdc++.h>
using namespace std;
int n;
char s[1000001];
struct Suffixautomaton{
struct edge{
int to,next;
}e[3000001];
int cnt=1,last=1,g[2000001][27],pa[2000001],len[2000001],head[2000001],indo[2000001],sz[2000001],tot,id[2000001];
inline void insert(int ch){
int pos=last,newp=++cnt;
last=newp;len[newp]=len[pos]+1;
for (;pos&&!g[pos][ch];pos=pa[pos]) g[pos][ch]=newp;
if (!pos) pa[newp]=1;
else{
int posx=g[pos][ch];
if (len[pos]+1==len[posx]) pa[newp]=posx;
else{
int vs=++cnt;
len[vs]=len[pos]+1;
for (int i=0;i<26;i++) g[vs][i]=g[posx][i];
pa[vs]=pa[posx];pa[posx]=pa[newp]=vs;
for (;g[pos][ch]==posx;pos=pa[pos]) g[pos][ch]=vs;
}
}
}
inline void bfs(){
for (int i=1;i<=cnt;i++){indo[len[i]]++;}
for (int i=1;i<=cnt;i++){indo[i]+=indo[i-1];}
for (int i=1;i<=cnt;i++){id[indo[len[i]]--]=i;}
for (int i=cnt;i>=1;i--){
sz[id[i]]=1;
for (int j=0;j<26;j++){
int v=g[id[i]][j];
if (!v) continue;
sz[id[i]]+=sz[v];
}
}
}
inline void query(int k){
int os=1;
while (k){
for (int i=0;i<26;i++){
if (g[os][i]){
if (sz[g[os][i]]>=k){
putchar('a'+i);
os=g[os][i];
k--;break;
}else k-=sz[g[os][i]];
}
}
}
puts("");
}
}sam;
int main(){
scanf("%s",s);
int L=strlen(s);
for (int i=0;i<L;i++) sam.insert(s[i]-'a');
sam.bfs();
int Q;
cin>>Q;
while (Q--){
int W;scanf("%d",&W);
sam.query(W);
}
}