hdu 4003 Find Metal Mineral(树形DP)

      题意:给一棵树,从一个节点到下一个节点需要消耗一定的能量,以最小的能量消耗遍历这棵树。
      解法:首先试着思考一个问题:如果有多个机器人去往一个子节点,那有多少机器人需要回来?
      假如从father节点去遍历它的子节点son,首先要明白一点如果所有的机器人都要回来,那还不如只让一个机器人去。因为在遍历一个节点的时候,如果要返回的话,那么需要消耗的能量是双倍的。比如说对于son节点,如果只去一个机器人,消耗是2*father->son->cost(注意需要返回);如果让两个机器人去,那么消耗就是4*father->son->cost。
       那么多个机器人能给我们带来的好处是什么呢?
       就是在不返回的情况下,某些节点我们可以少走一次(去了就不要回来了)。同样以a和b遍历son这个子树为例子,如果son还有两个son分别是son1和son2。如果我们让一个机器人去,消耗就是2*son->son1->cost+son->son2->cost;如果让两个机器人去,消耗就是son->son1->cost+son->son2->cost。可以看出来,在任何情况下,返回多个(超过一个)机器人都是不明智的,因为在这样情况下,多出的机器人并不能节省任何能源,但是在这个机器人返回son的父节点father节点的时候还需要多消耗一个father->son->cost。

       这时候我们再考虑遍历一个子树son所消耗的能量,在去的机器人数量不同的情况下消耗的能量是不同的。这里可以参考《背包九讲》中泛化背包的思想。至于优化方案的时候跟之前做的1011和1561不太一样,这个不只是只能取一个,还是必须去一个。开始的时候想的是把dp数组初始化为-1来解决这个问题,但是这样写起来代码需要多加一些判断还要对叶节点进行特别处理,看起来不是很美观。再然后看到了这种直接给每一个包一个初始值,然后再进行计算的方式,这样看起来代码漂亮得多。

       附上大神的博客:点击打开链接

#include<stdio.h>
#include<string.h>
#define N 10005
#define K 12
int dp[N][K],ans[K][N];
int n,s,k;
struct node
{
    int son;
    int next;
    int data;
}Edge[N*2];
int cnt,head[N],vis[N];
int Min(int x,int y)
{
    if(x<y) return x;
    else return y;
}
void AddEdge(int x,int y,int k)
{
    Edge[cnt].data=k;Edge[cnt].son=y;Edge[cnt].next=head[x];head[x]=cnt++;
    Edge[cnt].data=k;Edge[cnt].son=x;Edge[cnt].next=head[y];head[y]=cnt++;
    return ;
}
void dfs(int father)
{
    int u=head[father];
    vis[father]=1;
    for(int i=u;i!=-1;i=Edge[i].next)
    {
        int son=Edge[i].son;
        if(vis[son]) continue;
        dfs(son);
        for(int t=k;t>=1;t--)
        {
            dp[father][t]=Min(dp[father][t-1]+dp[son][1]+Edge[i].data,dp[father][t]+dp[son][0]+2*Edge[i].data);
            for(int j=1;j<=t;j++)
                dp[father][t]=Min(dp[father][t],dp[father][t-j]+dp[son][j]+j*Edge[i].data);
        }
        dp[father][0]+=dp[son][0]+2*Edge[i].data;
    }
    return ;
}
int main()
{
    while(scanf("%d%d%d",&n,&s,&k)!=EOF)
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        for(int i=1;i<n;i++)
        {
            int x,y,k;
            scanf("%d%d%d",&x,&y,&k);
            AddEdge(x,y,k);
        }
        memset(vis,0,sizeof(vis));
        memset(dp,0,sizeof(dp));
        dfs(s);
        printf("%d\n",dp[s][k]);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值