后缀树

朴素的后缀树就是字符串的所有后缀构成的 \(trie\) 树,这样树大小是 \(n^2\) 量级的,可以使用 \(SA\) 将朴素后缀树中的链压缩成一个节点,将节点数量控制在线性范围内.

首先,构建 \(SA\)\(height\).

压缩 \(trie\) 树每个节点存完整树高 \(depth\),也就是从 \(root\) 到该节点构成的字符串长度.

维护一个 \(depth\) 的单调栈,按 \(SA\) 从左往右处理所有后缀,每次弹出单调栈中 \(depth \gt height_i\) 的节点,记录最后一个被弹出的节点 \(last\),记现在栈顶节点为 \(top\),要加入的新节点为 \(cur\),显然 \(depth_{cur} = n-sa_i+1\).

  • \(depth_{top} = height_i\),将 \(cur\) 连到 \(top\) 上,将 \(cur\) 入栈.

  • \(depth_{top} \lt height_i\),在 \(last\)\(top\) 之间插入一个 \(depth\)\(height_i\) 的新节点,将 \(cur\) 连到该节点上,依次将中间节点和 \(cur\) 入栈.

可以发现,每次最多新建两个节点,树大小是线性的.

另外,这样构造出来的后缀树不能保证所有字符串结束节点都是后缀树的叶子,因为可能会出现两个后缀互为前缀的情况,如果想将结束节点全部作为叶子,可以在原字符串结尾加入一个小于字符集所有字符的唯一结束符,例如 \(\$\).

例题:洛谷P3804

\(s\) 的子串中出现次数大于 \(1\) 的子串的出现次数乘长度的最大值.

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve(){
    string s;
    cin >> s;

    s += '$';
    int n = s.size();
    s = ' '+s;

    vector<int> sa(n+1),rk(n+1),height(n+1);
    {
        int m = 255;
        vector<int> cnt(max(n,m)+1),ord(n+1);

        for (int i=1;i<=n;i++){
            rk[i] = (unsigned char)s[i];
            cnt[rk[i]]++;
        }

        for (int i=1;i<=m;i++){
            cnt[i] += cnt[i-1];
        }

        for (int i=n;i>=1;i--){
            sa[cnt[rk[i]]--] = i;
        }

        for (int k=1;;k<<=1){
            int tot = 1;
            for (int i=n-k+1;i<=n;i++){
                ord[tot++] = i; 
            }   

            for (int i=1;i<=n;i++){
                if (sa[i]>k){
                    ord[tot++] = sa[i]-k;
                }
            }

            fill(cnt.begin(),cnt.begin()+m+1,0);
            for (int i=1;i<=n;i++){
                cnt[rk[ord[i]]]++;
            }

            for (int i=1;i<=m;i++){
                cnt[i] += cnt[i-1];
            }

            for (int i=n;i>=1;i--){
                sa[cnt[rk[ord[i]]]--] = ord[i];
            }

            swap(rk,ord);
            rk[sa[1]] = 1;

            for (int i=2;i<=n;i++){
                pair<int,int> p1 = {ord[sa[i-1]],sa[i-1]+k<=n?ord[sa[i-1]+k]:-1};
                pair<int,int> p2 = {ord[sa[i]],sa[i]+k<=n?ord[sa[i]+k]:-1};
                rk[sa[i]] = rk[sa[i-1]]+(p1<p2);
            }

            m = rk[sa[n]];
            if (m==n) break;
        }

        int k = 0;
        for (int i=1;i<=n;i++){
            if (rk[i]==1) continue;
            int j = sa[rk[i]-1];

            while (i+k<=n && j+k<=n && s[i+k]==s[j+k]){
                k++;
            }

            height[rk[i]] = k;
            k = max(k-1,0);
        }
    }

    vector<int> fa{0},depth{0};
    stack<int> stk;
    stk.push(0);

    for (int i=1;i<=n;i++){
        int last = -1;
        while (!stk.empty() && depth[stk.top()]>height[i]){
            last = stk.top();
            stk.pop();
        }

        fa.push_back(0);
        depth.push_back(n-sa[i]+1);
        int cur = fa.size()-1;

        if (depth[stk.top()]==height[i]){
            fa[cur] = stk.top();
            stk.push(cur);
        }
        else{
            fa.push_back(0);
            depth.push_back(height[i]);
            int temp = fa.size()-1;
            fa[temp] = stk.top();
            
            if (last!=-1){
                fa[last] = temp;
            }

            fa[cur] = temp;
            stk.push(temp);
            stk.push(cur);
        }
    }

    int tot = fa.size();
    vector<vector<int>> adj(tot);
    for (int i=1;i<tot;i++){
        adj[fa[i]].push_back(i);
    }

    ll mx = 0;
    function<int(int)> dfs = [&](int u){
        int leaf = 0;
        for (auto& v:adj[u]){
            leaf += dfs(v);
        }

        if (adj[u].size()==0) leaf++;
        if (leaf>1) mx = max(mx,(ll)depth[u]*leaf);
        return leaf;
    };
    dfs(0);

    cout << mx << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int t = 1;
    // cin >> t;
    while (t--) solve();

    return 0;
}
posted @ 2026-08-13 12:34  kzssCCC  阅读(6)  评论(0)    收藏  举报