打打打打打卡

📅 2026/7/17 22:18:27 👁️ 阅读次数 📝 编程学习
打打打打打卡

一道很好的树形dp问题

P10974 Accumulation Degree

题目简述

给定一棵树,每条边有一个正容量。度为 1 的节点称为终端节点(叶子节点)。

定义:

  • A(x):从节点 x 出发,能够流向其他所有终端节点的最大流量之和。
  • 累积度:所有节点中最大的 A(x) 值。

任务是求给定树的累积度。

核心思想

对于任意节点 u,它向叶子节点输送的流量可以分为两个方向:

  1. 向子树方向(不经过父节点)
  2. 向父节点方向(经过父节点)

因此:

text

A(u) = d[u] + up[u]

其中:

  • d[u]:u 向子树方向(子节点方向)能输送的最大流量
  • up[u]:u 通过父节点方向能获得的最大流量

算法步骤

第一步:第一次 DFS(自底向上)

计算每个节点的 d[u](向下流量)。

递推公式

对于 u 的每个子节点 v:

  • 如果 v 是叶子节点(deg[v] == 1):

    text

    d[u] += w(u, v)
    
  • 如果 v 不是叶子节点:

    text

    d[u] += min(d[v], w(u, v))
    

为什么取 min?
从 u 流向 v 方向的流量,受两个因素限制:

  1. (u, v) 的容量
  2. v 向下输送的能力 d[v]
    所以取两者最小值。

叶子节点处理
叶子节点没有子节点,所以在 DFS 时跳过递归,直接累加边容量。


第二步:第二次 DFS(换根,自顶向下)

利用父节点的累积度,计算子节点的累积度。

核心公式

已知父节点 u 的累积度 f[u],要计算子节点 v 的累积度 f[v]

text

flow_to_v = min(d[v], w(u, v))   // u 流向 v 的流量
other = f[u] - flow_to_v          // u 除 v 方向外的其他流量
up_from_parent = min(other, w(u, v))  // v 通过父节点方向获得的流量
f[v] = d[v] + up_from_parent

特殊情况
当 u 是叶子节点时(deg[u] == 1),u 没有其他方向可以提供流量,所以:

text

f[v] = d[v] + w(u, v)

根节点
根节点没有父节点,所以:

text

f[root] = d[root]

第三步:求答案

遍历所有节点,取 f[i] 的最大值。

注意:当 n == 1 时,没有叶子节点(度为1的节点不存在),此时累积度为 0。


时间复杂度

  • 两次 DFS 各遍历所有节点一次:O(n)
  • 总时间复杂度:O(n)
  • 空间复杂度:O(n)

代码实现

#include <bits/stdc++.h>
using namespace std;const int N = 2e5 + 10;int T, n;
vector<pair<int, int>> g[N];
int d[N];      // d[u]:向下流量
int f[N];      // f[u]:累积度 A(u)
int deg[N];    // 度数// 第一次DFS:计算向下流量 d[u]
void dfs1(int u, int fa) {d[u] = 0;for (auto [v, w] : g[u]) {if (v == fa) continue;if (deg[v] == 1) {// v是叶子节点,不递归d[u] += w;} else {// v不是叶子节点,先递归再计算dfs1(v, u);d[u] += min(d[v], w);}}
}// 第二次DFS:换根计算累积度 f[u]
void dfs2(int u, int fa) {for (auto [v, w] : g[u]) {if (v == fa) continue;if (deg[u] == 1) {// u是叶子节点,只能提供w的流量f[v] = d[v] + w;} else {int flow_to_v = min(d[v], w);int other = f[u] - flow_to_v;f[v] = d[v] + min(other, w);}dfs2(v, u);}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> T;while (T--) {cin >> n;// 初始化for (int i = 1; i <= n; i++) {g[i].clear();d[i] = f[i] = deg[i] = 0;}// 读入边for (int i = 1; i < n; i++) {int u, v, w;cin >> u >> v >> w;g[u].push_back({v, w});g[v].push_back({u, w});deg[u]++;deg[v]++;}// 特殊情况:只有一个节点if (n == 1) {cout << 0 << '\n';continue;}int root = 1;// 第一次DFSdfs1(root, -1);// 根节点的累积度 = 向下流量f[root] = d[root];// 第二次DFS(换根)dfs2(root, -1);// 求最大值int ans = 0;for (int i = 1; i <= n; i++) {ans = max(ans, f[i]);}cout << ans << '\n';}return 0;
}