动态规划:如何求解最大连通节点值

题目

有N个小球,上面有数字,分别对应1……N,每个小球有个价值,第i个小球价值对应Value[i];有N-1个木棍,一个木棍两端分别连接着一个小球,把N个小球连接起来,并且保证任意两个小球间都不存在两条不同的路径可以互相到达。

现在要把1号小球连通的M个刷上油漆(连通指的是这一些涂漆的小球可以互相到达并且不会经过没有涂漆的小球),要求使这M个小球的值得和最大。

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第一行为两个整数N、M,意义如前文所述。

每组测试数据的第二行为N个整数,其中第i个整数Vi表示标号为i的结点的评分

每组测试数据的第3~N+1行,每行分别描述一根木棍,其中第i+1行为两个整数Ai,Bi,表示第i根木棍连接的两个小球的编号。

对于100%的数据,满足N<=10^2,1<=Ai<=N, 1<=Bi<=N, 1<=Vi<=10^3, 1<=M<=N

测试样例

样例输入

10 4
370 328 750 930 604 732 159 167 945 210 
1 2
2 3
1 4
1 5
4 6
4 7
4 8
6 9
5 10

样例输出

2977

解法

f(t, m)表示,在以t为根的一棵树中,选出包含根节点t的m个连通的结点,能够获得的最高的评分,然后我们的答案就是f(1, M)。

首先我要包含根节点,然后与根节点连通的结点最开始便是根节点的子结点,而所有选择的结点都要互相连通的话,那么如果选择某一棵子树中的结点的话就势必也需要选择这棵子树的根节点——所以就变成了一个规模小一些的子问题。比如在求解f(t, m)的时候,我先枚举t的第一个子结点t1中选出的结点数m1,然后枚举t的第二个子结点t2中选出的结点数m2……一直到t的最后一个子结点tk中选出的结点数mk,这样状态转移方程为:

f(t, m) = max{f(t1, m1) + f(t2, m2) + …… + f(tk, mk)} + v(t),并且需要保证m1+m2+…+mk+1=m

这算法根本无法计算,很难求解f(tk, mk),m1…mk可能有的方案数可是非常多。

其实这个问题非常像完全背包问题。我们将每个t的每个子节点所代表的子树看作是一件物品,相当于是在k个子树中选取M个,使获得的评分最高。则状态转移方程可以变成:

f[t, m] = max{ f[t, m - j] + f[child(i), j] },其中i的取值范围为t所有的子节点个数, 0 =< j <= M 
单独对于每棵子树,我们可以取0....M个节点,然后 遍历每棵子树,求出所有这些结果中的最大值即为f[t, m].

对任意一个节点t,计算f[t][0……M]
set f[t][0……M]=0;
f[t][1]=v[t];
f[t][0]=0;

for i = 0……节点t的孩子总数
    for m = M……2
        for j=0……m-1 //j为选取的子树(以child为根节点的子树)的节点数,j=0表示不选取该child(i)节点
             f[t][m]=max(f[t][m-j]+f[child(i)][j]);

注意:这个问题跟完全背包问题还是有一些根本区别的。对于完全背包问题,每一种物品的价值是一定的,取若干件物品所获得的最大价值满足加法分配率f[child][j + i] = f[child][j]  + f[child][i] ;但是对于此问题,不满足加法分配率,即 f[child][3] != f[child][2]  + f[child][1] .很显然对于以child为根节点的子树,取一点、二个点、三个点所获得的最大值是完全不同的,不等于简单地相加。

完整代码

#include<iostream>
#include<vector>
#include<algorithm>
std::vector<std::vector<int> > G(101);
int Value[101];
int f[101][101];
///pre上一个访问结点;current当前访问结点;M:current为根的树种,结点个数
void Visit(int pre, int current, int M)
{
    if (M == 1)
        return;
    for (int i = 0; i < G[current].size(); ++i)
    {
        if (G[current][i] == pre)
            continue;
        Visit(current, G[current][i], M - 1);
    }

    for (int i = 0; i < G[current].size(); ++i)
    {
        if (G[current][i] == pre)
            continue;
        for (int m = M; m >= 2; --m)
        {
            for (int m_child = 1; m_child < m; ++m_child)
            {
                f[current][m] = std::max(f[current][m], f[current][m - m_child] + f[G[current][i]][m_child]);
            }
        }
    }
}
int main()
{
    int N, M;
    std::cin >> N >> M;
    for (int i = 1; i <= N; ++i)
    {
        std::cin >> Value[i];
        f[i][1] = Value[i];
        f[i][0] = 0;
    }


    int Ai, Bi;
    for (int i = 1; i < N; ++i)
    {
        std::cin >> Ai >> Bi;
        G[Ai].push_back(Bi);
        G[Bi].push_back(Ai);
    }
    Visit(1, 1, M);
    std::cout << f[1][M] << std::endl;


}





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值