题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=4003
题意:一颗有根树N个节点的树和K个机器人,问把所有点都走到的最小花费
树上分组背包问题
这道题有一个要求就是这个机器人走完一颗树之后,还是可以回到树的根节点的,但也可以又机器人走这颗子树后不回到树根。
但是我们只要仔细的分析一下就可以看出来,假如一颗子树存在又机器人进去后又走回到树根,那么这个树只可能只放了一个机器人,且这个机器人走完这颗子树后回到树根。
情况1.有p(p > 1 )个机器人走这颗树,且它们都回到了树根。此时的花费是:子树边权和加上p个机器人回到根节点的路径和。此时假如只有一个机器人走这颗子树,花费为:子树边权和加上一条回到树根的路劲和。所以一个机器人的花费一定小于p个机器人的花费
情况2.有p(p > 1 )个机器人走这颗树,只有一个机器人回到了根节点,此时的花费和一个机器人走又回来是一样的:子树边权和加上一条回到树根的路径和。所以我们可以去掉剩下p - 1个没有回到根节点的机器人,这样不会更差还能节省机器人。
情况3.有p(p > 1) 个机器人走这颗子树,有q(1 < q < p)个机器人回到树根,那么同样花费为树上边权和 加上 p个回到树根的路径和,用一个机器人代替能得到更优的答案。
以上,在最优状态关于有机器人会回到树根的时候,只可能是1个机器人进入然后回到这个树根。
于是我们用
i = 0,对于子树rt有1个机器人进入然后又回到树根
i > 0,对于子树rt又i个机器人进入没有回到树根
对于每一颗子树,都做一个分组背包,每一个儿子的不同情况分到一组。背包容量为机器人个数k,物品价值为dp[son][j],代价为[j]。
i == 0 :
i>0:
代码:
#include <bits/stdc++.h>
#define sf scanf
#define pf printf
using namespace std;
const int maxn = 10000 + 5,maxm = 10 + 5,INF = 0x3f3f3f3f;
int n,s,k;
struct edge{
int v,c,pre;
}edges[maxn * 2];
int head[maxn],tot;
void init_edge(){
memset(head,-1,sizeof head),tot = 0;
}
void Insert_Edge(int u,int v,int c){
edges[tot].c = c;
edges[tot].v = v;
edges[tot].pre = head[u];
head[u] = tot++;
}
int dp[maxn][maxm];
/** dp[i][0] 表示第i个子树中放入了1个机器人 且这个机器人走完这个树后 回到节点i */
void DFS(int rt,int fa){
memset(dp[rt],0,sizeof dp[rt]);
for(int i = head[rt];~i;i = edges[i].pre){ //背包分组
int v = edges[i].v,c = edges[i].c;
if(v == fa) continue;
DFS(v,rt);
//分组背包
//枚举容量
for(int j = k;j >= 0;--j){
dp[rt][j] += dp[v][0] + 2 * c;
//枚举物品
for(int p = 1;p <= k;++p){
if(p > j) break;
dp[rt][j] = min(dp[rt][j],dp[rt][j - p] + dp[v][p] + c * p);
}
}
}
}
int main(){
while(~sf("%d %d %d",&n,&s,&k)){
init_edge();
for(int i = 1;i < n;++i){
int u,v,c;sf("%d %d %d",&u,&v,&c);
Insert_Edge(u,v,c);
Insert_Edge(v,u,c);
}
DFS(s,0);
int ans = INF;
for(int i = 0;i <= k;++i) ans = min(ans,dp[s][i]);
pf("%d\n",ans);
}
return 0;
}