高级树算法

掌握树的直径、重心分解和高级树形DP技术

概述

高级树算法是解决复杂树问题的核心技术。本章节深入探讨树的直径算法、重心分解、虚树构造等高级技术,为处理大规模树上查询和优化问题提供强大工具。

核心内容

1. 树的直径算法

// 树的直径算法
#include <iostream>
#include <vector>
#include <queue>
using namespace std;

class TreeDiameter {
private:
    int n;
    vector<vector<pair<int, long long>>> adj;
    
    // BFS找最远节点
    pair<int, long long> bfs(int start) {
        vector<long long> dist(n, -1);
        queue<int> q;
        q.push(start);
        dist[start] = 0;
        
        int farthest = start;
        long long maxDist = 0;
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            
            for (auto [v, weight] : adj[u]) {
                if (dist[v] == -1) {
                    dist[v] = dist[u] + weight;
                    q.push(v);
                    
                    if (dist[v] > maxDist) {
                        maxDist = dist[v];
                        farthest = v;
                    }
                }
            }
        }
        
        return {farthest, maxDist};
    }
    
    // DFS计算直径
    pair<long long, long long> dfs(int u, int parent) {
        long long max1 = 0, max2 = 0; // 最长和次长路径
        
        for (auto [v, weight] : adj[u]) {
            if (v != parent) {
                auto [childMax, childDiameter] = dfs(v, u);
                diameter = max(diameter, childDiameter);
                
                long long pathLen = childMax + weight;
                if (pathLen > max1) {
                    max2 = max1;
                    max1 = pathLen;
                } else if (pathLen > max2) {
                    max2 = pathLen;
                }
            }
        }
        
        diameter = max(diameter, max1 + max2);
        return {max1, diameter};
    }
    
    long long diameter;
    
public:
    TreeDiameter(int vertices) : n(vertices), diameter(0) {
        adj.resize(n);
    }
    
    void addEdge(int u, int v, long long weight = 1) {
        adj[u].push_back({v, weight});
        adj[v].push_back({u, weight});
    }
    
    // 方法1:两次BFS
    long long getDiameterBFS() {
        if (n == 0) return 0;
        
        // 第一次BFS找一个端点
        auto [node1, dist1] = bfs(0);
        
        // 第二次BFS找另一个端点
        auto [node2, diameter] = bfs(node1);
        
        return diameter;
    }
    
    // 方法2:一次DFS
    long long getDiameterDFS() {
        if (n == 0) return 0;
        
        diameter = 0;
        dfs(0, -1);
        return diameter;
    }
    
    // 获取直径路径
    vector<int> getDiameterPath() {
        if (n == 0) return {};
        
        auto [node1, dist1] = bfs(0);
        
        // 修改BFS记录路径
        vector<long long> dist(n, -1);
        vector<int> parent(n, -1);
        queue<int> q;
        q.push(node1);
        dist[node1] = 0;
        
        int node2 = node1;
        long long maxDist = 0;
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            
            for (auto [v, weight] : adj[u]) {
                if (dist[v] == -1) {
                    dist[v] = dist[u] + weight;
                    parent[v] = u;
                    q.push(v);
                    
                    if (dist[v] > maxDist) {
                        maxDist = dist[v];
                        node2 = v;
                    }
                }
            }
        }
        
        // 重构路径
        vector<int> path;
        int current = node2;
        while (current != -1) {
            path.push_back(current);
            current = parent[current];
        }
        
        return path;
    }
};

// 重心分解
class CentroidDecomposition {
private:
    int n;
    vector<vector<int>> adj;
    vector<bool> removed;
    vector<int> subtreeSize;
    
    // 计算子树大小
    int calculateSize(int u, int parent) {
        subtreeSize[u] = 1;
        for (int v : adj[u]) {
            if (v != parent && !removed[v]) {
                subtreeSize[u] += calculateSize(v, u);
            }
        }
        return subtreeSize[u];
    }
    
    // 找重心
    int findCentroid(int u, int parent, int treeSize) {
        for (int v : adj[u]) {
            if (v != parent && !removed[v] && subtreeSize[v] > treeSize / 2) {
                return findCentroid(v, u, treeSize);
            }
        }
        return u;
    }
    
    // 分解过程
    void decompose(int u, int parent) {
        int treeSize = calculateSize(u, -1);
        int centroid = findCentroid(u, -1, treeSize);
        
        removed[centroid] = true;
        
        // 处理重心
        processCentroid(centroid);
        
        // 递归分解子树
        for (int v : adj[centroid]) {
            if (!removed[v]) {
                decompose(v, centroid);
            }
        }
    }
    
    // 处理重心的逻辑(可自定义)
    void processCentroid(int centroid) {
        cout << "处理重心: " << centroid << endl;
        
        // 这里可以添加具体的处理逻辑
        // 例如:计算经过重心的路径
    }
    
public:
    CentroidDecomposition(int vertices) : n(vertices) {
        adj.resize(n);
        removed.resize(n);
        subtreeSize.resize(n);
    }
    
    void addEdge(int u, int v) {
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    
    void decompose() {
        fill(removed.begin(), removed.end(), false);
        decompose(0, -1);
    }
};

// 虚树构造
class VirtualTree {
private:
    int n, timer;
    vector<vector<int>> adj;
    vector<int> depth, parent, tin, tout;
    vector<vector<int>> up; // 二进制提升
    
    void dfs(int u, int p, int d) {
        tin[u] = timer++;
        depth[u] = d;
        parent[u] = p;
        up[u][0] = p;
        
        for (int i = 1; i < up[u].size(); i++) {
            if (up[u][i-1] != -1) {
                up[u][i] = up[up[u][i-1]][i-1];
            }
        }
        
        for (int v : adj[u]) {
            if (v != p) {
                dfs(v, u, d + 1);
            }
        }
        
        tout[u] = timer++;
    }
    
    bool isAncestor(int u, int v) {
        return tin[u] <= tin[v] && tout[u] >= tout[v];
    }
    
    int lca(int u, int v) {
        if (isAncestor(u, v)) return u;
        if (isAncestor(v, u)) return v;
        
        for (int i = up[u].size() - 1; i >= 0; i--) {
            if (up[u][i] != -1 && !isAncestor(up[u][i], v)) {
                u = up[u][i];
            }
        }
        
        return up[u][0];
    }
    
public:
    VirtualTree(int vertices) : n(vertices), timer(0) {
        adj.resize(n);
        depth.resize(n);
        parent.resize(n);
        tin.resize(n);
        tout.resize(n);
        
        int logN = 0;
        while ((1 << logN) <= n) logN++;
        up.assign(n, vector<int>(logN, -1));
    }
    
    void addEdge(int u, int v) {
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    
    void preprocess(int root = 0) {
        timer = 0;
        dfs(root, -1, 0);
    }
    
    // 构造虚树
    vector<vector<int>> buildVirtualTree(vector<int>& important) {
        if (important.empty()) return {};
        
        // 按DFS序排序
        sort(important.begin(), important.end(), [&](int a, int b) {
            return tin[a] < tin[b];
        });
        
        // 添加LCA节点
        vector<int> nodes = important;
        for (int i = 0; i < important.size() - 1; i++) {
            int l = lca(important[i], important[i + 1]);
            nodes.push_back(l);
        }
        
        // 去重并排序
        sort(nodes.begin(), nodes.end());
        nodes.erase(unique(nodes.begin(), nodes.end()), nodes.end());
        sort(nodes.begin(), nodes.end(), [&](int a, int b) {
            return tin[a] < tin[b];
        });
        
        // 构造虚树
        vector<vector<int>> vtree(n);
        vector<int> stack;
        
        for (int u : nodes) {
            while (!stack.empty() && !isAncestor(stack.back(), u)) {
                stack.pop_back();
            }
            
            if (!stack.empty()) {
                vtree[stack.back()].push_back(u);
            }
            
            stack.push_back(u);
        }
        
        return vtree;
    }
};

int main() {
    // 测试树的直径
    TreeDiameter td(6);
    td.addEdge(0, 1, 2);
    td.addEdge(1, 2, 3);
    td.addEdge(1, 3, 1);
    td.addEdge(3, 4, 4);
    td.addEdge(3, 5, 2);
    
    cout << "BFS方法直径: " << td.getDiameterBFS() << endl;
    cout << "DFS方法直径: " << td.getDiameterDFS() << endl;
    
    vector<int> path = td.getDiameterPath();
    cout << "直径路径: ";
    for (int i = 0; i < path.size(); i++) {
        cout << path[i];
        if (i < path.size() - 1) cout << " -> ";
    }
    cout << endl;
    
    // 测试重心分解
    CentroidDecomposition cd(7);
    cd.addEdge(0, 1);
    cd.addEdge(1, 2);
    cd.addEdge(1, 3);
    cd.addEdge(3, 4);
    cd.addEdge(3, 5);
    cd.addEdge(5, 6);
    
    cout << "重心分解:" << endl;
    cd.decompose();
    
    // 测试虚树
    VirtualTree vt(8);
    vt.addEdge(0, 1);
    vt.addEdge(0, 2);
    vt.addEdge(1, 3);
    vt.addEdge(1, 4);
    vt.addEdge(2, 5);
    vt.addEdge(2, 6);
    vt.addEdge(6, 7);
    
    vt.preprocess(0);
    
    vector<int> important = {3, 4, 7};
    vector<vector<int>> vtree = vt.buildVirtualTree(important);
    
    cout << "虚树构造完成" << endl;
    
    return 0;
}

解题技巧

🎯 算法选择

树的直径用两次BFS或一次DFS,重心分解处理路径问题,虚树优化特定节点查询。

⚡ 复杂度分析

直径算法O(n),重心分解O(n log n),虚树构造O(k log k),k为重要节点数。

🔧 实现要点

重心分解要正确维护removed数组,虚树构造需要LCA预处理和DFS序排序。

🧮 应用场景

树上距离查询、路径统计、子树操作优化等高级树问题的高效解决方案。