欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[BZOJ3238][Ahoi2013]差异(后缀自动机)

程序员文章站 2022-04-17 18:32:38
...

还是字符串题。

Source

https://www.lydsy.com/JudgeOnline/problem.php?id=3238

Solution

出于练手的目的,用 SAM 把此题再写了一遍,再次了解了 SAM 的妙用。
后缀数组做法:https://blog.csdn.net/xyz32768/article/details/78991972
首先要了解:后缀自动机 SAM 如何求原串两个前缀的最长公共后缀。
先找到这两个前缀在 SAM 中对应的状态 u,v
此外,我们知道,在 Parent 树中不断地往上跳就是不断地寻找后缀的过程。
因此,这两个前缀的最长公共后缀就是 Parent 树上 uv 的 LCA 状态能表示出的最长子串。
于是可以将原串倒过来,那么后缀的最长公共前缀就变成了前缀的最长公共后缀。问题转化为:构建好 SAM 和 Parent 树,以及一些关键点(能表示出原串的前缀的状态),求每对点 (u,v)

MaxlLCA(u,v)

之和。
Maxli 为状态 i 能表示的最长子串长度。
由于在 Parent 树上往上跳是 Right 集不断合并的过程,因此对于一个状态 u ,它的 Right 集大小等于 u 的子树内关键点的个数。
所以,对于一个点 u ,它是
|Rightu|×(|Rightu|1)2vson(u)|Rightv|×(|Rightv|1)2

对点的 LCA 。
上式的理解: u 的子树里的 |Rightu|×(|Rightu|1)2 对点都有公共祖先 u ,但如果一对点同时在 v 的子树内( vu 在 Parent 树上的子状态),那么这样的点对的 LCA 就不是 u ,必须去除。
于是一个状态 u1i<jnlcs(Suffixi,Suffixj) 的贡献为:
(|Rightu|×(|Rightu|1)2vson(u)|Rightv|×(|Rightv|1)2)×Maxlu

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
#define SAM(hnoi) for (; i && hnoi; i = T[i].fa)
#define ParentTree(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1e6 + 5;
struct cyx {
    int fa, go[26], ri, maxl;
    void init() {fa = ri = maxl = 0; memset(go, 0, sizeof(go));}
} T[N];
int n, QAQ, lst, ecnt, nxt[N], adj[N], go[N]; char s[N]; ll ans;
void add_edge(int u, int v) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}
void extend(int c) {
    int i = lst; T[lst = ++QAQ].init(); T[lst].maxl = T[i].maxl + 1;
    T[lst].ri = 1; SAM(!T[i].go[c]) T[i].go[c] = lst;
    if (!i) T[lst].fa = 1; else {
        int j = T[i].go[c]; if (T[i].maxl + 1 == T[j].maxl) T[lst].fa = j;
        else {
            int p; T[p = ++QAQ] = T[j]; T[p].ri = 0;
            T[lst].fa = T[j].fa = p; T[p].maxl = T[i].maxl + 1;
            SAM(T[i].go[c] == j) T[i].go[c] = p;
        }
    }
}
void dfs(int u) {
    ParentTree(u) dfs(v), T[u].ri += T[v].ri;
    ll cnt = 1ll * T[u].ri * (T[u].ri - 1); ParentTree(u)
        cnt -= 1ll * T[v].ri * (T[v].ri - 1); cnt >>= 1;
    ans += cnt * T[u].maxl;
}
void SuffixAutomaton() {
    int i; For (i, 2, QAQ) add_edge(T[i].fa, i); dfs(1);
}
int main() {
    int i; scanf("%s", s + 1); n = strlen(s + 1); T[QAQ = lst = 1].init();
    Rof (i, n, 1) extend(s[i] - 'a'); SuffixAutomaton();
    cout << (1ll * (n - 1) * n * (n + 1) >> 1) - (ans << 1) << endl; return 0;
}
相关标签: Suffix Automaton