搜集钻石

题目描述
某国有 n座城市,编号从 1到 n,城市间有 n−1 条道路,且保证任意两座城市之间是连通的。每一座城
市有一定数量的钻石。小明想在该国搜集钻石。他从城市 1出发,每天他可以通过城市之间道路开车到另
外的城市。当小明头第一次到一个城市的时候,他可以搜集完这个城市的所有钻石,如果他后面再来到这
个城市,就没有砖石可以收集了。  小明只有 K天时间,请你帮算小明计算他最多可以搜集多少钻石。
 输入格式: 第一行输入三个整数 n(1≤n≤100),K(0≤k≤200),表示一共有 n 座城市,小明有 K 天时间。 接下里一行输入 n 个用空格隔开的整数,表示每个城市的钻石数量。每个城市的钻石数量不大于 1000。接下来输入 n−1 行,每行输入两个整数 a(1≤a≤n),b(1≤b≤n),表示城市 a和城市 b之间存在一条双向道路。
 输出格式: 输出一行一个整数表示小明最大能获取的钻石数量。
 样例输入1
 3 2
 3 8 3
 1 3
 3 2
 样例输出1
 14
 样例输入2
 6 2
 5 9 8 4 9 2
 1 6
 6 2
 2 5
 5 3
 5 4
 样例输出2
 16

初步分析
这道题是背包问题但既不是01背包也不是多重背包,但总的思想还是分解容量(天数)来更新最大值,不过是用新的子树来更新罢了。
我们设dp[node][j][0],dp[node][j][1]分别表示结点node除法经过j天未回来和回来搜索到钻石的最大值。
那么该怎么更新呢。
1,要回来:dp[node][j][1],先用e天从node除法再回来,(花一天到更新的子节点v)再花j-e-2天从新增结点v出发再回来,最后回到结点node.用这条路上收集到的钻石来更新最大值。所以dp[node][j][1]=max(dp[node][j][1],dp[node][e][1]+dp[v][j-e-2][1])由于是v新增结点所以这两部分不会有路径重复所以能直接相加。
2,不回来:dp[node][j][0],这种状态可以分为两种情况。(其实也可以理解成三种情况不过等会讲)。
A:先到新增子结点v,再从新增子节点v出发花j-e-2天回来,再从node花e天不回来。
B:从node出发花e天回来,再到新增结点然后话j-e-1天不回来。
所以dp[node][j][0]=max(dp[node][j][0],dp[v][j-e-2][1]+dp[node][e][0])
dp[node][j][0]=max(dp[node][j][0],dp[node][e][1]+dp[v][j-e-1][0])
聪明的你有没有想到为什么第二个式子不考虑+dp[v][j-e-1][1]的情况呢。其实很容易能想到(dp[v][j-e-1][0]>=dp[v][j-e-1][1])。(PS:说实话我也是看了别人的代码才想到的,自己做的时候写的状态方程根本实行不了。)
那我们刚才说的第三种情况你知道是什么嘛?其实就是新增子树或者原来的node数根本不经过的情况,也就是不回来的情况。不过当j-e-2=0,j-e-1=0,e=0的时候就已经把这种情况考虑进去了。
还要注意的一点就是j的值应该从大到小,因为0=<e<=j-1,保证dp[node][e][1]的值一直是原来未新增子节点之前的最大值。有点类似于01背包的一维空间压缩。

解析得差不多啦,这道题确实困了我好久。网上看的几篇大佬的解析感觉不是讲得很清楚。希望大家能看懂我这个啰嗦的版本。自己也要动脑筋哦

直接上代码

 #include <iostream>

#include <string.h>
using namespace std;
const int MAX_N=110;
const int MAX_M=220;
const int MAX_K=220;

int n,k;
int dp[MAX_N][MAX_K][2];
int w[MAX_N];

int ans=0;
int head[MAX_N];
struct edge{
    int v;
    int next;
}eid[MAX_M];

void insert(int u,int v){
    eid[ans].v=v;
    eid[ans].next=head[u];
    head[u]=ans++;
}

void dfs(int node,int father){
   for(int i=0;i<=k;++i){
       dp[node][i][0]=w[node];
       dp[node][i][1]=w[node];
   }

   for(int i=head[node];i!=-1;i=eid[i].next){
       int v=eid[i].v;
       if(v==father) continue;
       dfs(v,node);

       for(int j=k;j>=1;--j){
           for(int e=0;e<=j-1;++e){
               if(j-e>=2){
                   dp[node][j][1]=max(dp[node][j][1],dp[node][e][1]+dp[v][j-e-2][1]);
                   dp[node][j][0]=max(dp[node][j][0],dp[v][j-e-2][1]+dp[node][e][0]);
               }
               dp[node][j][0]=max(dp[node][j][0],dp[node][e][1]+dp[v][j-e-1][0]);
           }
       }
   }
}
int main() {
    memset(head,-1, sizeof(head));
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i){
        scanf("%d",&w[i]);
    }

    int x,y;
    for(int i=1;i<=n-1;++i){
        scanf("%d%d",&x,&y);
        insert(x,y);
        insert(y,x);
    }

    dfs(1,-1);

    cout<<dp[1][k][0]<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值