HDU 4003 Find Metal Mineral (树形DP+分组背包)

来源:互联网 发布:java c 应用领域 编辑:程序博客网 时间:2024/03/29 15:02

Problem Description

Humans have discovered a kind of new metal mineral on Mars which are distributed in point‐like with paths connecting each of them which formed a tree. Now Humans launches k robots on Mars to collect them, and due to the unknown reasons, the landing site S of all robots is identified in advanced, in other word, all robot should start their job at point S. Each robot can return to Earth anywhere, and of course they cannot go back to Mars. We have research the information of all paths on Mars, including its two endpoints x, y and energy cost w. To reduce the total energy cost, we should make a optimal plan which cost minimal energy cost.


Input

There are multiple cases in the input.
In each case:
The first line specifies three integers N, S, K specifying the numbers of metal mineral, landing site and the number of robots.
The next n‐1 lines will give three integers x, y, w in each line specifying there is a path connected point x and y which should cost w.
1<=N<=10000, 1<=S<=N, 1<=k<=10, 1<=x, y<=N, 1<=w<=10000.


Output

For each cases output one line with the minimal energy cost.


Sample Input

3 1 1
1 2 1
1 3 1
3 1 2
1 2 1
1 3 1


Sample Output

3
2


Hint

In the first case: 1->2->1->3 the cost is 3;
In the second case: 1->2; 1->3 the cost is 2;


题目大意

人类发现了一种新的矿物,它们以点状分布,形成一棵树。人类发射 K 个机器人来收集它们,机器人的起点为S,求采完所有矿物的最小花费?给定所有路径的信息,包括其两个端点x,y和能量成本w。(有多组数据)


解题思路

本题是树形dp套分组背包,说实话,对于新接触这类题的人来说,这题真心不好想。像我,看课件也琢磨了挺久才算彻底明白解法。

我们一步步分析此题。

首先易知一个机器人如果在一个子树,那么在这个子树没有遍历完之前是不会跑到其他子树去的,因为要遍历完整棵树,假设此机器人跑到其他子树去,也一定会有一个机器人会遍历完当前子树,而任意一个机器人是等价的,这相当与在走回头路,这肯定没有机器人优先遍历完当前子树更优。

于是,我们发现决策的顺序即dp的阶段先处理完子树在处理根,这契合一般树形dp的模式(taolu)。

于是我们开始着手于如何记状态。我们设dp[root][j]表示对于以root结点为根结点的子树,放j个机器人所需要的最小代价(这j个机器人将待在子树里)。
当j=0时表示放了一个机器人下去,遍历完结点后又回到root结点了。
状态转移方程类似背包问题。而根据套路,树形dp中每棵子树可看成一组,这一组取且仅取一个状态。于是做分组背包。

dp[root][j] = min(dp[root][j], dp[root][j-k]+dp[v][k]+k*l);

其中l是树边长度(即花费)。

那么如何保证必选一个呢?
我们将dp[v][0]放进背包就可以了。如果该组里有更好的选择,那么就会换掉这个物品,保证了必选一个的情况。


什么是树形分组背包

对于每个根节点root,有个容量为K的背包,如果它有i个儿子,那么就有i组物品,价值分别为dp[v1][0],dp[v2][1]…..dp[vi][w] ,这些物品的重量分别为0,1,…..w。(即是说每个子树都看成一组背包,这里面还有一些背包嵌套)
现在要求从每组里选一个物品(且必须选一个物品)装进root的大背包里,使得容量不超过k的情况下价值最大。

分组背包写法:

设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有:
f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于组k}

使用一维数组的“分组背包”伪代码如下:
for 所有的组i
for v=V..0(注意由于滚动一定逆序)
for 所有的k属于组i
f[v]=max{f[v],f[v-c[k]]+w[k]}
//注意循环顺序


Code

#include <iostream>#include <cstdio>#include <cstdlib>#include <cstring>#include <algorithm>#include <cmath>#define N 10010#define K 15using namespace std;int n, k, s, dp[N][K];int cur, head_p[N];struct Tadj{    int next, obj, len;    Tadj() {}    Tadj(int _next, int _obj, int _len){      next = _next, obj = _obj, len = _len;    }}Edg[N<<1];void Insert(int a, int b, int c){    cur ++;    Edg[cur] = Tadj(head_p[a], b, c);    head_p[a] = cur;}void dfs(int root, int fa){    for(int i = head_p[root]; ~ i; i = Edg[i].next){      int v = Edg[i].obj, l = Edg[i].len;      if(v == fa)  continue;      dfs(v, root);      for(int j = k; j >= 0; j--){        dp[root][j] += dp[v][0] + 2 * l;        for(int t = 1; t <= j; t++)          dp[root][j] = min(dp[root][j], dp[root][j-t] + dp[v][t] + l * t);      }    }}int main(){    while(~ scanf("%d%d%d", &n, &s, &k)){      cur = -1;      for(int i = 1; i <= n; i++)  head_p[i] = -1;      memset(dp, 0, sizeof(dp));      int a, b, c;      for(int i = 1; i < n; i++){        scanf("%d%d%d", &a, &b, &c);        Insert(a, b, c);        Insert(b, a, c);      }      dfs(s, 0);      printf("%d\n", dp[s][k]);    }    return 0;}//提交几次都超时,原来是读入反了,RT...

这里写图片描述

Tell me what you want to hear
Something that will like those ears
Sick of all the insincere
So I’m gonna give all my secrets away
——secrets

2 0