【树形dp题解】dfs的巧妙应用

【树形dp题解】dfs的巧妙应用

[P2986 USACO10MAR] Great Cow Gathering G - 洛谷

  • 题目大意:

    Bessie 正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会。当然,她会选择最方便的地点来举办这次集会。

    每个奶牛居住在 N N N 个农场中的一个,这些农场由 N − 1 N-1 N1 条道路连接,并且从任意一个农场都能够到达另外一个农场。道路 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=,ji)。这可以从底向上计算得到

读者不妨认真体会以下代码。我给出一些理解:在自底向上计算时,一般先处理本层逻辑,在回溯时累加

由于此题可以任选根节点进行 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 long

const 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值