后缀自动机基础应用

本博客讲解后缀自动机基础应用,而不说明定义、构建等内容。
构建代码如下:(跑得飞慢)

struct node {
    node* ch[26], *f;
    int len, siz; // siz即Right集合大小
};
node* _nd = (node*)malloc(SIZE_OF_SAM); int noden;
node* root, *last;
inline node* newnode(node* fa) {
    node* ret = _nd+(noden++);
    if(fa) ret->len = fa->len + 1; else ret->len = 0; ret->siz = 0;
    return ret;
}

inline void extend(int x) {
    node* o = newnode(last), *p = last; o->siz = 1;
    last = o;
    for(; p && !p->ch[x]; p = p->f) p->ch[x] = o;
    if(!p) {o->f = root; return;}
    node* q = p->ch[x];
    if(q->len == p->len + 1) o->f = q;
    else{
        node* nq = newnode(p);
        memcpy(nq->ch, q->ch, sizeof(q->ch));
        nq->f = q->f; q->f = o->f = nq; 
        for(; p && p->ch[x] == q; p = p->f) p->ch[x] = nq;
    }
}

其中,每次新建的*o节点是前缀节点,对应 |Right|=1 | R i g h t | = 1 。其他*nq节点是非前缀节点。
如不加特别说明,以下fa的意义是parent树上的父亲,len的意义是这个状态对应的最长字符串的长度。由后缀自动机性质,其最短长度为len[fa]+1。后缀自动机DAG的意义是后缀自动机由字符转移边构成的DAG。

拓扑序

根据len的性质,可用以下代码求出后缀自动机DAG的拓扑序:

int topo_b[maxn], topo[maxn];
inline void get_topo() {
    for(int i = 0; i < noden; i++) topo_b[(_nd+i)->len]++;
    for(int i = 1; i < noden; i++) topo_b[i] += topo_b[i-1];
    for(int i = 0; i < noden; i++) topo[--topo_b[(_nd+i)->len]] = i;
}

用拓扑序可以替代用dfs遍历后缀自动机来统计某些信息。

求所有状态Right集合大小

在一开始,只有前缀节点siz=1。
从儿子到父亲统计即可。即对每个状态u,siz[fa]+=siz[u]。

for(int i = noden-1; i >= 0; i--) {
    node* o = (_nd+topo[i]);
    if(o->f) o->f->siz += o->siz;
}

|Right| | R i g h t | 的意义是这个状态中的串出现了多少次。
若要统计本质不同不重叠重复子串个数,可以记录 Right R i g h t 的最左位置和最右位置进行判断。

统计子串

在后缀自动机DAG上从root到一个点的一段路径是一个子串。
在后缀自动机DAG上,一个点能到达的点的数量代表以这个状态为前缀的不同子串个数。一个点能达到的点的 |Right| | R i g h t | 之和代表以这个状态为前缀的子串个数
以下为求可到达子串的代码:(siz的定义分两种)注意root的答案只能从儿子合并。

for(int i = noden-1; i >= 0; i--) {
    node* o = (_nd+topo[i]);
    if(o != root) o->sum = o->siz;
    for(int j = 0; j < 26; j++) if(o->ch[j]) o->sum += o->ch[j]->sum;
}

由此我们看到,parent树和后缀自动机DAG各有所用。

[bzoj3998] 求第k大的子串和本质不同子串。
分别对应上述两种情况。查询时类似XX树上二分跳即可。

前缀的最长公共后缀

答案=len[lca(x, y)]。lca指在parent树上的lca。容易理解。

[bzoj3238] 求两两后缀的最长公共前缀。
反建后缀自动机即可。

一建一跑

本质是后缀自动机上的DP。与其他自动机上DP类似,DP状态中记录到自动机中的哪个节点即可。
常用状态:f[i][j][…]表示当前枚举到字符串i位,在自动机上j节点的状态。

广义后缀自动机

将多个串建在一个后缀自动机上。
考虑将多个串建在一起的情况,可以发现一些特性。
同于将多个串连起来建自动机的做法,较于复杂。
参见练习题。

动态维护Parent树信息

如果只用维护Parent树当然不用LCT。但我们要维护每个点的 |Right| | R i g h t | 。前文的做法是在建完后缀自动机后再线性统计的,不能支持修改操作。
用LCT维护Parent树,可以支持在线在主串末尾添加字符串的操作并维护每个节点的 |Right| | R i g h t | 。如果有其他信息也可以维护。

维护所有状态的Right集合

在Parent树上,一个节点的Right集合是它所有儿子节点的Right集合的并。
用动态开点树、平衡树、set等都可以做。

mip xtc caff soi ab gos sor HAHAHA!

点赞