19 ACwing 287 积蓄程度 题解
积蓄程度
题面
给定一个 \(n\) 个点,\(n - 1\) 条边的树形流网络,每条边都有容量
对于每个度数为 1 的点,将其当做源点(根),其余度数为 1 的点当做汇点,求最大流
\(n \le 2\times 10^5\)
题解
这道题不难想到朴素的 \(O(n^2)\) 做法,就是枚举哪个点为根,然后dfs一遍求得答案
但是这样过不了这题,所以就要用到我们的换根法,能够在 \(O(n)\) 的时间复杂度内解决本题
首先我们设 \(f(x)\) 表示以 1 为源点时从 \(x\) 节点能够流出去的最大流量,\(g(x)\) 表示以 \(x\) 为源点能够流出去的最大流量
首先 \(f\) 数组我们可以 \(O(n)\) 做一遍dfs求出,而后要思考的是如何求出 \(g\) 数组
对于 \(1\) ,\(g(1) = f(1)\)
那么我们利用类似数学归纳法的方式来推导这个dp过程
假设我们已经求出了 \(g(x)\) ,设 \(y \in son_x\)
\[g(y) = f(y) + min \{ e(x, y),\ g(x) - min \{e(x,y),\ f(y)\} \}
\]
这样我们就可以在 \(O(n)\) 的时间复杂度内求出结果
注意一些细节
- 一开始选的根不能是某个叶子节点,否则后面换根的时候某个节点的父节点会成为出水口,不方便统计,所以我们保证一开始选的根为非叶子结点
- 如果换根时儿子节点是叶子,那么就不加 \(f(y)\)
具体细节看代码
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 2e5 + 10, M = N << 1;
const int INF = (1ll << 31) - 1;
int n;
int h[N], ver[M], ne[M], e[M], tot;
int f[N], g[N], deg[N];
bool leaf[N];
void add (int x, int y, int z) {
ver[++tot] = y;
ne[tot] = h[x];
h[x] = tot;
e[tot] = z;
}
void dfs1 (int x, int fa) {
f[x] = 0;
bool fn = 0;
for (int i = h[x]; i; i = ne[i]) {
int y = ver[i];
if (y == fa) continue;
fn = 1;
dfs1 (y, x);
f[x] += min (f[y], e[i]);
}
if (!fn) {
leaf[x] = 1;
f[x] = INF;
}
}
void dfs2 (int x, int fa) {
for (int i = h[x]; i; i = ne[i]) {
int y = ver[i];
if (y == fa) continue;
if (leaf[y]) {
g[y] = min (e[i], g[x] - min (e[i], f[y]));
} else {
g[y] = min (e[i], g[x] - min (e[i], f[y])) + f[y];
}
dfs2 (y, x);
}
}
void solve () {
memset (h, 0, sizeof h);
memset (leaf, 0, sizeof leaf);
memset (deg, 0, sizeof deg);
tot = 0;
cin >> n;
for (int i = 1; i < n; i ++) {
int x, y, z;
cin >> x >> y >> z;
add (x, y, z);
add (y, x, z);
deg[x] ++;
deg[y] ++;
}
int rt = 0;
for (int i = 1; i <= n; i ++) {
if (deg[i] > 1) {
rt = i;
break;
}
}
if (!rt) {
cout << e[1] << endl;
return;
}
dfs1 (rt, 0);
g[rt] = f[rt];
dfs2 (rt, 0);
int ans = 0;
for (int i = 1; i <= n; i ++) {
ans = max (ans, g[i]);
}
cout << ans << endl;
}
int main () {
int T;
cin >> T;
while (T --) {
solve ();
}
return 0;
}