洛谷 P6139 【模板】广义后缀自动机(广义 SAM) 题解

一、题目:

洛谷原题

二、思路:

这是一道模板题。网上的大部分写法太假了,所以就认真学了一下真的广义后缀自动机。其实还挺好懂的。

在线版

大概思路就是说每次遇到一个新串,就把las赋成1。但是这还没完,如果这样做的话,肯定会遇到\(node[las].ch[s]\)已经存在的情况。这时要分类讨论。

\(v\)\(las\)\(x\)\(node[v].ch[s]\)

  1. \(node[v].len+ 1 = node[x].len\)。说明当前子串已经完全处于自动机之中,我们直接将\(las\)赋成\(x\),便于下一次继续。
  2. \(node[v].len + 1 <node[x].len\)。我们需要从\(x\)拆分出一个点\(y\),类似普通的后缀自动机进行处理即可。

离线版

先把这堆串的trie树建出来。

发现trie的每一个节点代表一个不同前缀。所以只需要把这些不同的前缀插入到SAM中即可。

使用BFS即可解决。

三、代码:

// 在线版
#include <iostream>
#include <cstdio>
#include <cstring>

#define mem(s, v) memset(s, v, sizeof s)

using namespace std;

const int maxn = 1e6 + 5;

int n, las = 1, tot = 1;
char ch[maxn];

struct Node {
    int fa, len;
    int ch[27];
}node[maxn << 1];

inline int extend(int s) {
    int v = las;
    if (node[v].ch[s]) {
        int x = node[v].ch[s];
        if (node[v].len + 1 == node[x].len) return x;
        int y = ++ tot;
        node[y] = node[x];
        node[y].len = node[v].len + 1;
        node[x].fa = y;
        for (; v && node[v].ch[s] == x; v = node[v].fa) node[v].ch[s] = y;
        return y;
    }
    int z = ++ tot;
    node[z].len = node[v].len + 1;
    for (; v && node[v].ch[s] == 0; v = node[v].fa) node[v].ch[s] = z;
    if (!v) { node[z].fa = 1; return z; }
    int x = node[v].ch[s];
    if (node[x].len == node[v].len + 1) { node[z].fa = x; return z; }
    int y = ++ tot;
    node[y] = node[x];
    node[y].len = node[v].len + 1;
    node[z].fa = node[x].fa = y;
    for (; v && node[v].ch[s] == x; v = node[v].fa) node[v].ch[s] = y;
    return z;
}

int main() {
    int t; scanf("%d", &t);
    while (t --) {
        scanf("%s", ch + 1);
        n = strlen(ch + 1);
        las = 1;
        for (int i = 1; i <= n; ++ i) las = extend(ch[i] - 'a' + 1);
    }
    long long ans = 0;
    for (int i = 2; i <= tot; ++ i) {
        (ans += node[i].len - node[node[i].fa].len);
    }
    printf("%lld\n", ans);
    return 0;
}

// 离线版
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>

using namespace std;

const int maxn = 1e6 + 5;

char ch[maxn];
int trie[maxn][27], sz = 1, fa[maxn], c[maxn];
int pos[maxn], tot = 1;

struct Node {
    int fa, len;
    int ch[27];
}node[maxn << 1];

inline void insert(char *str, int n) {
    int p = 1;
    for (int i = 1; i <= n; ++ i) {
        if (!trie[p][str[i] - 'a' + 1]) trie[p][str[i] - 'a' + 1] = ++ sz, fa[sz] = p;
        p = trie[p][str[i] - 'a' + 1];
        c[p] = str[i] - 'a' + 1;
    }

}

inline int extend(int s, int las) {
    int z = ++ tot, v = las;
    node[z].len = node[v].len + 1;
    for (; v && node[v].ch[s] == 0; v = node[v].fa) node[v].ch[s] = z;
    if (!v) node[z].fa = 1;
    else {
        int x = node[v].ch[s];
        if (node[x].len == node[v].len + 1) node[z].fa = x;
        else {
            int y = ++ tot;
            node[y] = node[x];
            node[y].len = node[v].len + 1;
            node[x].fa = node[z].fa = y;
            for (; v && node[v].ch[s] == x; v = node[v].fa) node[v].ch[s] = y;
        }
    }
    return z;
}

inline void build(void) {
    queue<int>q;
    for (int i = 1; i <= 26; ++ i)
        if (trie[1][i])
            q.push(trie[1][i]);
    pos[1] = 1; // pos代表trie树上的节点在SAM中的编号
    while (q.size()) {
        int x = q.front(); q.pop();
        pos[x] = extend(c[x], pos[fa[x]]);
        for (int i = 1; i <= 26; ++ i)
            if (trie[x][i])
                q.push(trie[x][i]);
    }
}

int main() {
    int t; cin >> t;
    while (t --) {
        scanf("%s", ch + 1);
        insert(ch, strlen(ch + 1));
    }
    build();
    long long ans = 0;
    for (int i = 2; i <= tot; ++ i) {
        (ans += node[i].len - node[node[i].fa].len);
    }
    cout << ans << endl;
    return 0;
}

posted @ 2021-03-27 14:42  蓝田日暖玉生烟  阅读(54)  评论(0编辑  收藏  举报