树形DP ———— 【HDU4003】 Find Metal Mineral

题目

给一棵 n n n个节点的树, 节点编号为 1 1 1~ n n n, 每条边都有一个花费值。
k k k个机器人从 s s s点出发, 问让机器人遍历所有边,最少花费值多少?

分析

基本思路

Q:

指定节点出发,那么我们需不需要考虑从当前节点向子节点祖先节点两种情况呢?

A:

不需要,虽然是指定 s s s 出发,但是因为是无根树,所以我们依然可以只考虑子节点的情况,只需要把 s s s 当作树根即可


想到这里,我们应该已经意识到这是一道树形dp的问题,则子问题的划分显而易见:
从以 u u u 为根节点的子树出发,派出 k k k 个机器人,遍历完子树所需的最小代价

回边的细节分析

Q:

如果把 k k k 个机器人派出,那么他们还返不返回呢?

A:

显然,很可能要返回


那么这里又出现了一个很重要的问题:回边,这也是这道题的难点


特殊情况一:

如果只有一个机器人,且最后需要返回 s s s
一个很显然的结论是:每一条边显然要被走过两遍,总代价设为 s u m sum sum s u m = ∑ w ( u , v ) ∣ u , v 是树上相邻的节点, w ( u , v ) 是边权 sum=\sum w(u,v) |u,v是树上相邻的节点,w(u,v)是边权 sum=w(u,v)u,v是树上相邻的节点,w(u,v)是边权

特殊情况二:

如果只有一个机器人,最后不需要返回 s s s
那么我们如果想要代价最小,那么只遍历一次的边就要尽量的多
A n s = s u m − ∑ w ( u , v ) ∣ w ( u , v ) 只遍历一遍 Ans = sum - \sum w(u,v) | w(u,v)只遍历一遍 Ans=sumw(u,v)w(u,v)只遍历一遍
其实也就是减去一条从 s s s 到叶子节点的最长路径

类推到所有情况

假设一条边 w ( u , v ) w(u,v) w(u,v) k k k个机器人总共遍历了 x x x遍,则最后的总代价会减少 2 × w ( u , v ) − x × w ( u , v ) 2 \times w(u,v) - x \times w(u,v) 2×w(u,v)x×w(u,v) 我们要这个值最大

换一种方法理解这个式子,如果我们派出 k k k 个机器人,那么最后会返回几个呢?
很显然是 1 1 1 个,为什么呢?
如果我们让 y y y 个机器人返回,那么 w ( u , v ) w(u,v) w(u,v) 就会被走 y × w ( u , v ) y \times w(u,v) y×w(u,v)次,且子树内的边仍然是不变的
所以很显然更劣
那么 k 个 走一遍,最后1个返回,也就是 ( k + 1 ) × w ( u , v ) = x × w ( u , v ) (k + 1) \times w(u,v) = x \times w(u,v) (k+1)×w(u,v)=x×w(u,v) (因为总共是被遍历 x x x遍,又只有 1 1 1个遍历了两边所以 x = k + 1 x = k + 1 x=k+1

因此我们的 d p dp dp状态也就设好了,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示以 i i i 为根节点的子树内派出 j j j 个机器人,所减去的代价最大为多少
这样我们所要求的就是只遍历一遍的边:成功避免了回边问题

代码

#include 
using namespace std;
const int N = 1e4,MAXK = 10;
int n,s,k,sum;
vector<pair<int,int> > G[N + 5];
int dp[N + 5][MAXK + 5]; 
void dfs(int u,int f) {
	for(auto e: G[u]) {
		int v = e.first,w = e.second;
		if (v == f) continue;
		dfs(v,u);
		for(int i = k;i >= 1;i --) {
			for(int j = 1;j <= i;j ++) {
				dp[u][i] = max(dp[u][i],dp[u][i - j] + dp[v][j] + 2 * w - j * w);
			}
		}
	}
}
int main() {
	scanf("%d%d%d",&n,&s,&k);
	for(int i = 1,x,y,w;i < n;i++) {
		scanf("%d%d%d",&x,&y,&w);
		G[x].push_back({y,w});
		G[y].push_back({x,w});
		sum += w;
	}
	dfs(s,0);
	printf("%d",2 * sum - dp[s][k]);
	return 0;
}
/*
	一个机器人遍历整个树再返回根节点,花费 2 * sum_{wi}
	如果不返回,则花费 2 * sum_{wi} - max{路径长度}
	多个机器人,尽量让 max{只走一次的路径长度} 的和最长
*/

你可能感兴趣的:(信息学竞赛,算法,深度优先,图论)