1. 程式人生 > 其它 >LG5829 【模板】失配樹 題解

LG5829 【模板】失配樹 題解

Tag

字串,最近公共祖先。

Description

給定一個字串 \(S\),一共 \(m\) 次詢問,每一次詢問字串 \(S\) 的字首 \(p\) 與字首 \(q\) 的最長公共 border。
\(\texttt{data range:} |S| \leq 10^6, m \leq 10^5\).

Solution

有一個非常顯然的結論,就是一個字串的 border 一定包含了比其短的所有該字串的 border

對於找出一個字串的 border 我們有非常優秀的 KMP 演算法可以做到線性的時間複雜度求出一個單字串的所有字首的最長 border。

由於我們要求最長公共 border,所以直觀的感受一下將失配指標看成當前點的父親節點,然後形成的一顆樹就是整個字串的失配樹,我們要做的就是查詢兩個點的最近公共祖先就可以了。

還有一個性質就是所有點的父親節點一定小於當前節點,所以我們無需建樹,可以線性的預處理,\(O(\lg n)\) 的查詢兩個節點的最近公共祖先,用樹剖即可。

Code

constexpr int N = 1e6 + 10;

char s[N];

int fa[N], son[N], top[N], sz[N], dep[N];
int lca(int x, int y) {
    while(top[x] ^ top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }
    return dep[x] < dep[y] ? x : y;
}

void solve() {
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    for(int i = 2, j = 0; i <= n; i++) {
        while(j && s[j + 1] != s[i]) j = fa[j];
        if(s[i] == s[j + 1]) j++;
        fa[i] = j;
    }// KMP 過程
    ROF(i, n, 1) {
        sz[i]++;
        if(fa[i]) sz[fa[i]] += sz[i];
        if(sz[i] >= sz[son[fa[i]]]) son[fa[i]] = i;
    }
    FOR(i, 1, n) {
        dep[i] = dep[fa[i]] + 1;
        if(son[fa[i]] != i) top[i] = i;
        else top[i] = top[fa[i]];
    }// 樹剖預處理
    int q = rd;
    while(q--) {
        int x = fa[rd], y = fa[rd];
        cout << lca(x, y) << '\n';
    }
    return ;
}

Final

樹剖的常數大概是倍增 lca 常數的 \(\frac 23\) 左右,而且預處理的時間和空間複雜度都是線性的,非常優秀,開了 O2 之後是最優解第一面除了一幫子寫取模跳父親的人之外的最快的了。

細節:由於一個字首的 border 不能是他自己,所以一定要先往自己的父親跳一下。

安利:希望大家多寫樹剖 lca 不要寫倍增 lca,阿里嘎多。