【树形dp题解】dfs的巧妙应用
[P2986 USACO10MAR] Great Cow Gathering G - 洛谷
-
题目大意:
Bessie 正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会。当然,她会选择最方便的地点来举办这次集会。
每个奶牛居住在 N N N 个农场中的一个,这些农场由 N − 1 N-1 N−1 条道路连接,并且从任意一个农场都能够到达另外一个农场。道路 i i i 连接农场 A i A_i Ai 和 B i B_i Bi,长度为 L i L_i Li。集会可以在 N N N 个农场中的任意一个举行。另外,每个牛棚中居住着 C i C_i Ci 只奶牛。
在选择集会的地点的时候,Bessie 希望最大化方便的程度(也就是最小化不方便程度)。比如选择第 X X X 个农场作为集会地点,它的不方便程度是其它牛棚中每只奶牛去参加集会所走的路程之和(比如,农场 i i i 到达农场 X X X 的距离是 20 20 20,那么总路程就是 C i × 20 C_i\times 20 Ci×20)。帮助 Bessie 找出最方便的地点来举行大集会。
对于 d f s dfs dfs来说,我们可以从上到下计算,也可以从下到上计算。如果我们想知道每个节点距离根节点的距离 d i ( i ≠ r o o t ) d_i(i \neq root) di(i=root),那么我们不妨令 d r o o t = 0 d_{root} = 0 droot=0,通过自顶向下的搜索得到 d i ( i ≠ r o o t ) d_i(i \neq root) di(i=root)
不妨设 s z i sz_i szi表示以 i i i为根的子树中奶牛数量的和。此时需要自底向上计算
不妨设 f i f_i fi表示以节点 i i i为聚会地点时的最小不方便程度。对于以 i i i为根时树的叶子节点来说, f i = d i × C i ( i = 叶 子 节 点 ) f_i = d_i \times C_i(i = 叶子节点) fi=di×Ci(i=叶子节点)。对于以 i i i为根,非叶子节点的节点来说, f i = f j + d i × C i ( i ≠ 叶 子 节 点 , j 是 节 点 i 的 儿 子 节 点 ) f_i = f_j + d_i \times C_i(i \neq 叶子节点, j是节点i的儿子节点) fi=fj+di×Ci(i=叶子节点,j是节点i的儿子节点)。这可以从底向上计算得到
读者不妨认真体会以下代码。我给出一些理解:在自底向上计算时,一般先处理本层逻辑,在回溯时累加
由于此题可以任选根节点进行 d f s dfs dfs,这里我选择 1 1 1作为根节点
void dfs1(int u, int fa) {sz[u] = cnt[u];f[u] = d[u] * cnt[u];for (int i = h[u]; i != -1; i = ne[i]) {int j = e[i];if (j == fa) continue;d[j] = d[u] + w[i];dfs1(j, u);sz[u] += sz[j];f[u] += f[j];}
}
在预处理好上述含义数组后,我们思考转移方程。读者不妨画出图来,否则难以理解
假设根节点 u u u的儿子有 s o n 1 , s o n 2 son_1, son_2 son1,son2,若此时将 s o n 1 son_1 son1作为集会地点, f [ s o n 1 ] f[son_1] f[son1]可以 f [ u ] f[u] f[u]转移得到
转换集会地点前: f [ u ] = f [ s o n 1 ] + f [ s o n 2 ] f[u] = f[son_1] + f[son_2] f[u]=f[son1]+f[son2]
转换地点后: f [ s o n 1 ] f[son_1] f[son1]本来以根节点 1 1 1为集会地点,现在集会地点变为 j ( j 是 根 节 点 的 儿 子 ) j(j是根节点的儿子) j(j是根节点的儿子),转换后有 f [ s o n 1 ] = f [ s o n 1 ] − w [ i ] × s z [ s o n 1 ] f[son_1] = f[son_1] - w[i] \times sz[son_1] f[son1]=f[son1]−w[i]×sz[son1]
同理, f [ s o n 2 ] = f [ s o n 2 ] + w [ i ] × ( s z [ 1 ] − s z [ s o n 1 ] ) f[son_2] = f[son_2] + w[i] \times (sz[1] - sz[son_1]) f[son2]=f[son2]+w[i]×(sz[1]−sz[son1])
有 f [ j ] = f [ s o n 1 ] − w [ i ] × s z [ s o n 1 ] + f [ s o n 2 ] + w [ i ] × ( s z [ 1 ] − s z [ s o n 1 ] ) f[j] = f[son_1] - w[i] \times sz[son_1] + f[son_2] + w[i] \times (sz[1] - sz[son_1]) f[j]=f[son1]−w[i]×sz[son1]+f[son2]+w[i]×(sz[1]−sz[son1])
即 f [ j ] = f [ s o n 1 ] + f [ s o n 2 ] − w [ i ] × ( 2 s z [ s o n 1 ] − s z [ 1 ] ) = f [ u ] − w [ i ] × ( 2 s z [ s o n 1 ] − s z [ 1 ] ) f[j] = f[son_1] + f[son_2] - w[i] \times (2sz[son_1] - sz[1]) = f[u] - w[i] \times (2sz[son_1] - sz[1]) f[j]=f[son1]+f[son2]−w[i]×(2sz[son1]−sz[1])=f[u]−w[i]×(2sz[son1]−sz[1])
void dfs2(int u, int fa) {for (int i = h[u]; i != -1; i = ne[i]) {int j = e[i];if (j == fa) continue;f[j] = f[u] - (2 * sz[j] - sz[1]) * w[i];dfs2(j, u);}
}
完整代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define endl "\n"#define int long longconst int N = 100010, M = N * 2;
int h[N], e[M], ne[M], w[M], idx;
int cnt[N];
int n;
int res = INT_MAX;
int d[N], sz[N], f[N];void add(int a, int b, int c) {e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}void dfs1(int u, int fa) {sz[u] = cnt[u];f[u] = d[u] * cnt[u];for (int i = h[u]; i != -1; i = ne[i]) {int j = e[i];if (j == fa) continue;d[j] = d[u] + w[i];dfs1(j, u);sz[u] += sz[j];f[u] += f[j];}
}void dfs2(int u, int fa) {for (int i = h[u]; i != -1; i = ne[i]) {int j = e[i];if (j == fa) continue;f[j] = f[u] - (2 * sz[j] - sz[1]) * w[i];dfs2(j, u);}
}signed main() {ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);memset(h, -1, sizeof h);cin >> n;for (int i = 1; i <= n; i ++) {cin >> cnt[i];}for (int i = 1; i <= n - 1; i ++) {int u, v, x;cin >> u >> v >> x;add(u, v, x);add(v, u, x);}dfs1(1, -1);dfs2(1, -1);LL res = 1e18;for (int i = 1; i <= n; i ++) {res = min(res, f[i]);}cout << res << endl;return 0;
}