题解:AtCoder AT_awc0098_e Maintenance of Waterways
【题目来源】
AtCoder:E - Maintenance of Waterways
【题目描述】
There is a region consisting of \(N\) settlements. The road network of this region forms a tree structure, with settlement \(1\) as the root (central hub). Each settlement \(i\) (\(2 \leq i \leq N\)) has a parent settlement \(P_i\), and settlement \(i\) and settlement \(P_i\) are directly connected by a road. There are \(N - 1\) roads in total.
It has been decided to develop one waterway along each road. The construction of each waterway incurs a basic construction cost of \(1\). Furthermore, for each waterway construction, exactly one of the two settlements connected by the corresponding road must be chosen as the responsible settlement, and that settlement undertakes the construction. That is, for the waterway construction along the road connecting settlement \(i\) and settlement \(P_i\), either settlement \(P_i\) or settlement \(i\) is chosen to be responsible. Every waterway must be assigned to one of the settlements.
Here, the waterways that each settlement \(i\) can be responsible for are only those corresponding to roads that have settlement \(i\) as an endpoint. Specifically, if settlement \(i\) is not the root, the eligible waterways are those for "the edge connecting settlement \(i\) and its parent \(P_i\)" and "the edges connecting settlement \(i\) and each of its child settlements." For settlement \(1\) (the root), which has no parent, only the waterways for edges to its child settlements are eligible.
For each settlement \(i\) (\(1 \leq i \leq N\)), a construction capacity \(C_i\) and an additional cost coefficient \(W_i\) are defined. Let \(m_i\) be the number of waterway constructions assigned to settlement \(i\). If \(m_i\) is at most \(C_i\), no additional cost is incurred. However, if \(m_i\) exceeds \(C_i\), an additional cost of \(W_i\) is incurred for each excess waterway. That is, the additional cost incurred at settlement \(i\) is \(W_i \times \max(0,\, m_i - C_i)\).
The total cost is the sum of the basic construction costs of all waterways (always \(N - 1\)) plus the sum of additional costs incurred at all settlements:
\(\displaystyle (N - 1) + \sum_{i=1}^{N} W_i \times \max(0,\, m_i - C_i)\)
Determine the minimum total cost when the responsible settlement for each waterway is chosen optimally.
有一个由 \(N\) 个聚落组成的区域。该地区的道路网络呈树形结构,以聚落 \(1\) 为根(中心枢纽)。每个聚落 \(i\)(\(2 \leq i \leq N\))都有一个父聚落 \(P_i\),聚落 \(i\) 和聚落 \(P_i\) 之间由一条道路直接连接。总共有 \(N - 1\) 条道路。
现已决定沿每条道路修建一条水道。每条水道的修建产生 \(1\) 的基本建设费用。此外,对于每条水道的修建,必须且只能选择连接该道路的两个聚落之一作为负责聚落,由该聚落承担修建任务。也就是说,对于沿连接聚落 \(i\) 和聚落 \(P_i\) 的道路修建的水道,选择聚落 \(P_i\) 或聚落 \(i\) 作为负责聚落。每条水道都必须分配给某个聚落。
这里,每个聚落 \(i\) 能够负责的水道仅限于以该聚落为端点的道路所对应的水道。具体地,如果聚落 \(i\) 不是根,其可选的水道包括"连接聚落 \(i\) 与其父聚落 \(P_i\) 的边"以及"连接聚落 \(i\) 与其每个子聚落的边"所对应的水道。对于聚落 \(1\)(根),由于其没有父聚落,只有通往其子聚落的边所对应的水道是可选的。
对于每个聚落 \(i\)(\(1 \leq i \leq N\)),定义了建设容量 \(C_i\) 和额外费用系数 \(W_i\)。设 \(m_i\) 为分配给聚落 \(i\) 的水道修建数量。如果 \(m_i\) 不超过 \(C_i\),则不产生额外费用。但是,如果 \(m_i\) 超过 \(C_i\),则每超出一个水道就产生 \(W_i\) 的额外费用。也就是说,聚落 \(i\) 产生的额外费用为 \(W_i \times \max(0,\, m_i - C_i)\)。
总费用为所有水道的基本建设费用之和(恒为 \(N - 1\))加上所有聚落产生的额外费用之和:
求在 optimal 选择每条水道的负责聚落时,总费用的最小值。
【输入】
\(N\)
\(P_2\) \(P_3\) \(\ldots\) \(P_N\)
\(C_1\) \(W_1\)
\(C_2\) \(W_2\)
\(\vdots\)
\(C_N\) \(W_N\)
- The first line gives the number of settlements \(N\).
- The second line gives \(P_2, P_3, \ldots, P_N\), representing the parent settlements of settlements \(2, 3, \ldots, N\), separated by spaces.
- In the following \(N\) lines, the \(i\)-th line (\(1 \leq i \leq N\)) gives the construction capacity \(C_i\) and additional cost coefficient \(W_i\) of settlement \(i\), separated by a space.
【输出】
Output the minimum total cost in a single line.
【输入样例】
4
1 1 2
1 3
0 5
2 2
0 4
【输出样例】
7
【核心思想】
-
问题分析:给定 \(N\) 个聚落构成的树形结构,每条边需要修建水道并分配给其中一个端点负责。每个聚落 \(i\) 有建设容量 \(C_i\) 和额外费用系数 \(W_i\),若负责 \(m_i\) 条水道且 \(m_i > C_i\),则额外费用为 \(W_i \times (m_i - C_i)\)。求最小总费用(基本费用 \(N-1\) 加上额外费用)。这是一个树形DP + 贪心选择问题。
-
算法选择:
- 树形DP(Tree DP):以聚落 \(1\) 为根进行DFS,自底向上计算每个子树的最小额外费用
- 状态设计:
- \(dp0[u]\):\(u\) 到父节点的边不由 \(u\) 负责时,以 \(u\) 为根的子树的最小额外费用
- \(dp1[u]\):\(u\) 到父节点的边由 \(u\) 负责时,以 \(u\) 为根的子树的最小额外费用
- 贪心选择:对每个节点 \(u\),将"子边由子节点负责 vs 由 \(u\) 负责"的费用差排序,优先选择费用变化为负的方案
-
关键步骤:
- 初始化:读入 \(N\)、父节点数组 \(P[2..N]\)、每个聚落的 \(C_i\) 和 \(W_i\)
- 建图:使用链式前向星建立无向树
- DFS树形DP(自底向上):
- 对于节点 \(u\),遍历所有子节点 \(v\):
- 递归计算
dfs(v, u),得到 \(dp0[v]\) 和 \(dp1[v]\) - 默认假设:\(u-v\) 边由 \(v\) 负责,累加费用
cost += dp1[v] - 计算费用差:\(\Delta = dp0[v] - dp1[v]\),表示"改为由 \(u\) 负责"的额外费用变化。若 \(\Delta < 0\),说明由 \(u\) 负责更优
- 将所有 \(\Delta\) 存入数组
delta
- 递归计算
- 排序:
sort(delta),按升序排列,优先选择负值(由 \(u\) 负责更优) - 枚举 \(u\) 负责的子边数量 \(k\)(从 \(0\) 到 \(delta.size()\)):
sum= 前 \(k\) 个最小的 \(\Delta\) 之和(选择 \(k\) 条子边由 \(u\) 负责的额外费用)- \(dp0[u]\)(父边不由 \(u\) 负责):
- \(u\) 负责 \(m_0 = k\) 条边(\(k\) 条子边,不含父边)
- 费用 = \(cost + sum + W_u \times \max(0, m_0 - C_u)\)
- \(dp1[u]\)(父边由 \(u\) 负责):
- \(u\) 负责 \(m_1 = k + 1\) 条边(\(k\) 条子边 + \(1\) 条父边)
- 费用 = \(cost + sum + W_u \times \max(0, m_1 - C_u)\)
- 取所有 \(k\) 中的最小值作为 \(dp0[u]\) 和 \(dp1[u]\)
- 对于节点 \(u\),遍历所有子节点 \(v\):
- 输出答案:总费用 = \((N - 1) + dp0[1]\)(根节点无父边,使用 \(dp0\))
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N)\),每个节点对其子节点的 \(\Delta\) 数组排序,总排序复杂度为 \(O(N \log N)\)(也可用
nth_element优化至 \(O(N)\)) - 空间复杂度:\(O(N)\),存储树结构、DP数组和辅助数组
- 时间复杂度:\(O(N \log N)\),每个节点对其子节点的 \(\Delta\) 数组排序,总排序复杂度为 \(O(N \log N)\)(也可用
-
树形DP + 贪心选择的核心思想:
- 状态设计技巧:用 \(dp0\) 和 \(dp1\) 分别表示"父边是否由当前节点负责"两种状态,避免父子之间的决策耦合
- 贪心排序策略:对每个节点,将"子边由子节点负责"作为默认方案,计算"改由当前节点负责"的费用差 \(\Delta\)。排序后枚举选择数量 \(k\),保证在 \(O(k)\) 时间内找到最优组合
- 额外费用处理:额外费用 \(W_u \times \max(0, m - C_u)\) 是分段函数,当 \(m \leq C_u\) 时为 \(0\),超过后线性增长。通过枚举 \(k\) 自动处理所有可能情况
- 自底向上计算:子树的DP值先计算完成,父节点利用子节点的DP值进行组合,保证无后效性
- 适用于树形结构上的分配问题、匹配问题、容量限制类优化问题
【算法标签】
树形DP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 将 int 定义为 long long,防止数值溢出
const int N = 200005, M = 200005 * 2, INF = 1e18; // N: 最大节点数, M: 最大边数, INF: 无穷大
int n; // 聚落数量
int p[N]; // p[i]: 聚落i的父聚落
int C[N], W[N]; // C[i]: 建设容量, W[i]: 额外费用系数
// dp0[u]: 父边(u到父节点的边)不由u负责时,以u为根的子树的最小额外费用
// dp1[u]: 父边(u到父节点的边)由u负责时,以u为根的子树的最小额外费用
int dp0[N], dp1[N];
int h[N], e[M], ne[M], idx; // 链式前向星存图
// 链式前向星:添加无向边
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// DFS树形DP:计算以u为根的子树的最小额外费用
void dfs(int u, int fa)
{
int cost = 0; // cost: 所有子节点v的dp1[v]之和(假设所有子边都由子节点负责)
vector<int> delta; // delta: 存储每个子节点"父边由父节点负责 vs 由子节点负责"的费用差
// 遍历u的所有邻接点
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if (v == fa) continue; // 跳过父节点,只处理子节点
dfs(v, u); // 递归处理子树
// 先假设u到v的边由v负责(子节点负责),此时费用为dp1[v]
cost += dp1[v];
// delta记录:如果改为由u负责(父节点负责),费用变化为dp0[v] - dp1[v]
// 若delta<0,说明由u负责更优
delta.push_back(dp0[v] - dp1[v]);
}
// 将delta按升序排序,优先选择费用变化为负的(即由u负责更优的子边)
sort(delta.begin(), delta.end());
// 枚举u负责的子边数量k(从0到delta.size())
// 即:选择k条子边由u负责,其余由子节点负责
int min_dp0 = INF, min_dp1 = INF;
int sum = 0; // sum: 前k个最小的delta之和(即选择k条子边由u负责的额外费用)
for (int k = 0; k <= delta.size(); k++)
{
if (k > 0)
sum += delta[k - 1]; // 累加第k个delta(即再多选一条子边由u负责)
// 情况1:父边不由u负责(dp0[u])
// u负责的边数 = k(k条子边由u负责,父边不由u负责)
int m0 = k;
int cost0 = cost + sum + W[u] * max(0LL, m0 - C[u]);
min_dp0 = min(min_dp0, cost0);
// 情况2:父边由u负责(dp1[u])
// u负责的边数 = k + 1(k条子边 + 1条父边)
int m1 = k + 1;
int cost1 = cost + sum + W[u] * max(0LL, m1 - C[u]);
min_dp1 = min(min_dp1, cost1);
}
dp0[u] = min_dp0; // 记录父边不由u负责时的最小额外费用
dp1[u] = min_dp1; // 记录父边由u负责时的最小额外费用
}
signed main() // 使用 signed 替代 int,因为 #define int long long
{
cin >> n; // 读入聚落数量
// 初始化链式前向星
memset(h, -1, sizeof(h));
// 读入父节点信息,并建立无向边(树结构)
for (int i = 2; i <= n; i++)
{
cin >> p[i];
add(p[i], i), add(i, p[i]); // 双向建边
}
// 读入每个聚落的建设容量C和额外费用系数W
for (int i = 1; i <= n; i++)
cin >> C[i] >> W[i];
// 从根节点1开始DFS
dfs(1, 0);
// 输出最小总费用
// 总费用 = (N-1)条边的基本建设费用 + 最小额外费用
// dp0[1]:根节点没有父边,所以用dp0(父边不由根负责,实际上不存在父边)
cout << n - 1 + dp0[1] << endl;
return 0;
}
【运行结果】
4
1 1 2
1 3
0 5
2 2
0 4
7
浙公网安备 33010602011771号