hdu 4003 Find Metal Mineral(树形dp+分组背包)

Find Metal Mineral

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=4003

解题思路:

题目大意:

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

算法思想:

根据题意可以知道,机器人是可以走回头路的。所以分析下可知,如果从根节点派往子节点如果要折回到根节点,派向这个子节点

的机器人越多,产生的重复路径就会越多,所以那么派往这个子节点的个数为1时是最优策略。那么定义dp[i][0]存放1个机器人,从

子节点返回到根节点的花费。


现在思考:如果根节点s, 有 n个子节点,那么要怎么样选择才能使dp[s][k]的值最小呢?

⑴当机器人的个数为1时,那么只有一种方法,就是用这一个机器人跑遍每个子节点、也就是每次机器人都要从一个子节点返回根节

点,然后走向另一个节点,直到遍历所有点为止。那么这种状态下的状态转移为: dp[s][0]=dp[next][0]+2*w;       (其中s为根节点,

next为子节点,w为s到next的权值)。

⑵当机器人的个数>1时,那么必然存在机器人不必返回根节点的策略。同时由⑴也可以求出折回根节点的花费。

那么求根节点s下的n个子节点,把每个节点看做一个整体,定义dp(n,j)为前n个节点派j个机器人的最小花费,其实就是个包,dp(n,j)

dp(n-1,j)必然存在某种联系。可以推出dp(n,j)=min(dp(n,j), dp(n-1,j-k)+dp[v][k]+k*w);  (1<=k<=j,w为s到当前这个子节点v的权值),

因为只要用到上一个子节点的状态。所以对起始点s的所有子节点扫描一遍就可以了。

AC代码:

#include <bits/stdc++.h>
using namespace std;

const int maxn = 10005;
struct node{
    int v,w;
    node(int _v,int _w):v(_v),w(_w){}
};
vector<node> path[maxn];
int dp[maxn][15];//节点i,向子节点放j个机器人的最小花费
int n,s,k;

void dfs(int u,int father){
    int len = path[u].size();
    for(int i = 0; i < len; i++){
        int next = path[u][i].v;
        int w = path[u][i].w;
        if(father == next)
            continue;                     //如果当前的节点为父亲节点,不往下走。

        if(father == -1 || father != next){//如果u为s点 (最上面的那个根节点)或者next不等于父亲节点
            //cout<<next<<" "<<u<<endl,
            dfs(next,u);
        }

        //cout<<next<<endl;
        for(int j = k; j >= 1; j--){
            dp[u][j] += dp[next][0] + 2*w;
            for(int jj = 1; jj <= j; jj++)
                dp[u][j] = min(dp[u][j],dp[u][j-jj]+dp[next][jj]+jj*w);

        }
        dp[u][0] += dp[next][0] + 2*w;
    }
}

int main(){
    while(~scanf("%d%d%d",&n,&s,&k)){
        for(int i = 0; i <= 10000; i++)
            path[i].clear();
        memset(dp,0,sizeof(dp));
        int u,v,w;
        for(int i = 0; i < n-1; i++){
            scanf("%d%d%d",&u,&v,&w);
            path[u].push_back(node(v,w));
            path[v].push_back(node(u,w));
        }
        dfs(s,-1);
        printf("%d\n",dp[s][k]);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值