树的性质:利用树的唯一路径特性,避免复杂的最短路径计算。

定理:一定先完成同一子树下目标点的遍历,故需要记录父节点遍历次序,通过逆序bfs来实现自下而上,通过父节点记录来累加得到父节点的累计值(因为记录了父节点故有严格的两边关系,不需要按照dfs的后序来实现子树下的累加)

  1. 分步骤拆解
    建树:用邻接表存储树结构。

BFS预处理:计算父节点和拓扑序,为子树统计做准备。

统计任务点:初始化每个节点的cntS(取货点)和cntT(送货点)。

子树汇总:逆序处理拓扑序,自底向上累加子树的任务点数量。

计算总路程:遍历所有边,根据cntS和cntT判断是否经过,累加边权。

例子:6.11华为.1 https://niumacode.com/training/131/problem/P1687

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int main() {
    // 输入处理
    int N, M;
    cin >> N >> M;
    vector<vector<pair<int, int>>> adj(N + 1);
    for (int i = 0; i < N - 1; ++i) {
        int u, v, c;
        cin >> u >> v >> c;
        adj[u].push_back({v, c});
        adj[v].push_back({u, c});
    }

    // BFS建立父节点和拓扑序
    vector<int> fa(N + 1, 0), order;
    queue<int> q;
    q.push(1);
    fa[1] = 0;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        order.push_back(u);
        for (auto [v, c] : adj[u]) {
            if (v != fa[u]) {
                fa[v] = u;
                q.push(v);
            }
        }
    }

    // 初始化任务点统计
    vector<int> cntS(N + 1, 0), cntT(N + 1, 0);
    for (int i = 0; i < M; ++i) {
        int s, t;
        cin >> s >> t;
        cntS[s]++;
        cntT[t]++;
    }

    // 逆序汇总子树
    for (int i = order.size() - 1; i >= 0; --i) {
        int u = order[i];
        if (fa[u] != 0) {
            cntS[fa[u]] += cntS[u];
            cntT[fa[u]] += cntT[u];
        }
    }

    // 计算总路程
    long long sumS = 0, sumT = 0;
    for (int u = 2; u <= N; ++u) {
        int p = fa[u];
        for (auto [v, c] : adj[u]) {
            if (v == p) {
                if (cntS[u] > 0) sumS += c;
                if (cntT[u] > 0) sumT += c;
                break;
            }
        }
    }
    cout << 2 * (sumS + sumT) << endl;
    return 0;
}

第二题同样是树结构,但是主要和深度、叶子节点有关,故需要储存的是children数组

  • 输入处理:读取设备数量n和连接关系,构建邻接表adj。

  • DFS遍历:计算每个节点的深度depth和子节点列表children,同时记录最大深度max_depth。

  • 动态规划求解:
    对每个可能的深度h(从0到max_depth):按深度分层存储节点by_depth。
    初始化best数组,best[v]表示以v为根的子树在目标深度h时的最优解。
    从最大深度开始反向计算:如果节点深度大于h,设为极小值。
    如果节点深度等于h,设为1(保留该节点)。
    否则,累加子节点的有效解,并更新当前节点的best值。
    记录根节点的最优解answer。
    输出结果:需要移除的设备数为n - answer。

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    vector<vector<int>> adj;
    vector<int> depth;
    vector<vector<int>> children;
    int max_depth = 0;
    
    void dfs(int u, int p) {
        for (int v : adj[u]) {
            if (v == p) continue;
            depth[v] = depth[u] + 1;
            max_depth = max(max_depth, depth[v]);
            children[u].push_back(v);
            dfs(v, u);
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        int n;
        cin >> n;
        adj.resize(n + 1);
        depth.resize(n + 1);
        children.resize(n + 1);
    
        for (int i = 0; i < n - 1; ++i) {
            int u, v;
            cin >> u >> v;
            adj[u].push_back(v);
            adj[v].push_back(u);
        }
    
        dfs(1, 0);
    
        int answer = 0;
        for (int h = 0; h <= max_depth; ++h) {
            vector<vector<int>> by_depth(max_depth + 1);
            for (int v = 1; v <= n; ++v) {
                by_depth[depth[v]].push_back(v);
            }
    
            vector<int> best(n + 1, -1e9);
            for (int d = max_depth; d >= 0; --d) {
                for (int v : by_depth[d]) {
                    if (depth[v] > h) {
                        best[v] = -1e9;
                    } else if (depth[v] == h) {
                        best[v] = 1;
                    } else {
                        int s = 0;
                        for (int u : children[v]) {
                            if (best[u] > 0) {
                                s += best[u];
                            }
                        }
                        best[v] = (s > 0) ? (s + 1) : -1e9;
                    }
                }
            }
            answer = max(answer, best[1]);
        }
    
        cout << n - answer << endl;
    
        return 0;
    }