zoukankan      html  css  js  c++  java
  • hdu4003详解(树形dp+多组背包)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4003

    Find Metal Mineral

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65768/65768 K (Java/Others)
    Total Submission(s): 4490    Accepted Submission(s): 2071


    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;
     
    题意:给你一颗有n个节点的树,给出每两个相连节点边的权值(如果你的一个机器人要走这条边花费的能量),再给你k个机器人,问从s点出发,最少花费多少d能量可以遍历所有的节点。
     
    思路:一道很好的树形背包的题目,我们可以设立 dp 数组 dp[i][j] 表示节点 i 损失 j 个机器人遍历以节点 i 为根节点组成的子树的所有点所需的最小能量,什么是损失 j 个机器人呢?机器人不是可以来回走的,怎么会损失呢,这是以它最终停留的位置来说 ,我们定义当前节点损失机器人就是机器人从当前节点出发往它的子节点走之后最终没有到当前节点,这就相当于我们这个节点损失了机器人(因为它走之后就没有回来),如果它往子节点走之后又回到当前节点就相当于没有损失机器人(因为你派出它它又回来了,你又可以在当前节点重复利用它)。
     
    有了dp数组之后我们怎么来推状态转移方程呢,因为我们要遍历所有的数组,所以节点i来说,我们要遍历它的所有的子节点,即它每个子节点(遍历以该子节点组成的子树的所有点所需的花费)的dp值我们都要取一个,这就是一个多组背包的问题了(每组至少要取一个)(每一组的物品为损失不同机器人数需要的花费的最少能量)。
     
    因为是多组背包,为了保证每一组物品都至少取一个,所以我们的在执行递推之前先把当前子节点损失机器人数为0的先加到当前节点的花费里面(这就保证了每组至少取一个),它的状态转移方程就是
     
     
    dp[i][j] = dp[i][j] + dp[k][0]+ 2*f[i][k] ;
     
    其中 i 表示当前节点,j 代表当前节点损失的机器人数,k 表示当前节点的子节点,f[i][k] 表示i节点到 k 节点的花费,为什么是这样的呢,因为对于当前节点,它要到它的子节点去的时候,它的花费就是子节点的损失加上机器人从当前节点到子节点的花费,因为子节点损失的机器人数为0,那么说明所有机器人都回到了子节点,所以我只要派一个机器人去再让它回来就可以了就可以了
     
    看到上面这段肯定有人会懵,啥,为什么派出去了还要回来?      
     
    让我们再深入解析一下我们的dp数组, dp 数组 dp[i][j] 表示当前节点 i 花费它父节点j个机器人来遍历以 i 节点组成的子树的所有节点花费的最少能量(如果它花费 j=0 时表示它花费了父节点0个机器人遍历该子树所有的点所需的最少花费,不就是要回来吗,而那些损失数 j 大于零的,我就没必要遍历完子树的所有节点后再让它们回到 i 节点,因为没必要,会增加能量消耗,反正我要损失掉 i 父节点j 个机器人,它们都不回到 i 节点,不就相当于一开始我们说的 i 节点损失了 j 个机器人吗),这样就可以推出我们的dp方程
    dp[i][j]+=max(dp[i][j],dp[i][j-l]+dp[k][l]+l*f[i][k]);
    为什么是加 l*f[i][k] 呢,看了上面的解释应该不难理解,就是先从 i 派出 l 个机器人到 k 所需的花费.
     
    代码:
                                   
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    struct{//链式前向星 
        int v,w,next;
    }edge[20020];
    int head[10010];
    int cnt;
    bool vt[10010];
    int dp[10010][12];//dp[i][j]表示节点 i 损失(机器人最后不回到i节点) j 个机器人遍历以节点 i 为根节点组成的子树的所有点所需的最小能量
    void add(int u,int v,int w){
        edge[cnt].v=v;
        edge[cnt].w=w;
        edge[cnt].next=head[u];
        head[u]=cnt++;
    }
    int n,s,m;
    void dfs(int k){
        for(int i=0;i<=m;i++)//初始化为0 
        dp[k][i]=0; 
        vt[k]=true;
        for(int i=head[k];i!=-1;i=edge[i].next){
            if(!vt[edge[i].v]){
                dfs(edge[i].v);//先递归子节点 
                for(int j=m;j>=0;j--){
                    dp[k][j]+=dp[edge[i].v][0]+2*edge[i].w;//它儿子的花费的机器人是0,我就要派一个机器人去它那里然后再叫它回来 
                    for(int l=1;l<=j;l++){
                        dp[k][j]=min(dp[k][j],dp[k][j-l]+dp[edge[i].v][l]+edge[i].w*l);//它儿子要花费l个机器人,那我要派l个过去 
                    }
                }
                
            }
        }
    }
    int main(){
        int u,v,w;
        while(scanf("%d%d%d",&n,&s,&m)!=EOF){
            fill(head,head+10002,-1);//初始化 
            fill(vt,vt+10002,false);
            cnt=0;
            for(int i=1;i<n;i++){
                scanf("%d%d%d",&u,&v,&w);
                add(u,v,w);
                add(v,u,w);
            }
            dfs(s);    
            printf("%d
    ",dp[s][m]);
        }
        return 0;
    } 
                      
     
     
     
     
     
  • 相关阅读:
    枚举子集 Codeforces306 Div2 B
    UVA140 剪枝
    回溯法浅谈
    UVA10976
    UVA11059
    BZOJ3355
    hdu 2509 博弈 *
    博弈专题
    hdu 1404 找sg ***
    hdu 4759 大数+找规律 ***
  • 原文地址:https://www.cnblogs.com/cglongge/p/10311269.html
Copyright © 2011-2022 走看看