描述
上回说到,小Ho有着一棵灰常好玩的树玩具!这棵树玩具是由N个小球和N-1根木棍拼凑而成,这N个小球都被小Ho标上了不同的数字,并且这些数字都是处于1…N的范围之内,每根木棍都连接着两个不同的小球,并且保证任意两个小球间都不存在两条不同的路径可以互相到达。没错,这次说的还是这棵树玩具的故事!
小Ho的树玩具的质量似乎不是很好,短短玩了几个星期,便掉漆了!
“简直是一场噩梦!”小Ho拿着树玩具眼含热泪道。
“这有什么好忧伤的,自己买点油漆刷一刷不就行了?”小Hi表示不能理解。
“还可以这样?”小Ho顿时兴高采烈了起来,立马跑出去买回来了油漆,但是小Ho身上的钱却不够——于是他只买回了有限的油漆,这些油漆最多能给M个结点涂上颜色,这就意味着小Ho不能够将他心爱的树玩具中的每一个结点都涂上油漆!
小Ho低头思索了半天——他既不想只选一部分结点补漆,也不想找小Hi借钱,但是很快,他想出了一个非常棒的主意:将包含1号结点的一部分连通的结点进行涂漆(这里的连通指的是这一些涂漆的结点可以互相到达并且不会经过没有涂漆的结点),然后将剩下的结点拆掉!
那么究竟选择哪些结点进行涂漆呢?小Ho想了想给每个结点都评上了分——他希望最后留下来,也就是涂漆了的那些结点的评分之和可以尽可能的高!
那么,小Ho该如何做呢?
输入
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第一行为两个整数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
小Hi的Tip:那些用数组存储树边的记得要开两倍大小哦!
输出
对于每组测试数据,输出一个整数Ans,表示使得涂漆结点的评分之和最高可能是多少。
样例输入
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
思路
给了一棵n个节点的树,每个点都有点权,现在让你选择以1为根节点,选择m个联通的节点,使得所获得的权值最大。
我们可以定义:dp[t][m]
表示以t为根的树,选择m个联通的节点所获得的最大权值。
那么我们可以得到一个算法:
d p ( t , m ) = m a x { t 1 m 1 , t 2 m 2 , t 3 m 3 , . . . , t n m n } dp(t,m)=max\left \{ t_1m_1,t_2m_2,t_3m_3,...,t_nm_n \right \} dp(t,m)=max{t1m1,t2m2,t3m3,...,tnmn}
其中 t 1 , t 2 , t 3 . . . t n t_1,t_2,t_3...t_n t1,t2,t3...tn为 t t t的儿子,且 m 1 + m 2 + . . . m n = m m_1+m_2+...m_n=m m1+m2+...mn=m
但是这是一个指数级的算法,我们需要进行优化.
我们可以用树形dp的思想,定义:
- d f s ( t , m ) dfs(t,m) dfs(t,m)表示以t为根节点,选择t这个节点后,额外选择m个节点所能获得的最大权值.
- d p [ t ] [ m ] dp[t][m] dp[t][m]表示以t为根的树,选择m个联通的节点所获得的最大权值。
对于每一个节点,我们求出这个节点的所有 d p [ t ] [ 0... m ] dp[t][0...m] dp[t][0...m]的值,对于每一个可能的m进行一次背包,状态转移方程为:
d p [ i ] = m a x ( d p [ i ] , d p [ i − j ] + d f s ( v , j − 1 ) ) dp[i]=max(dp[i],dp[i-j]+dfs(v,j-1)) dp[i]=max(dp[i],dp[i−j]+dfs(v,j−1))表示当前选了i-j个获得的最大权值+当前选择当前节点,额外在选j-1个所获得的最大权值.
官方题解:
这里不是和无限背包问题很像么?我可以不用单独的求解每一个f(t, m)而是针对于每一个t,同时求解它的f(t, 0…M),这样的话,我就可以把m视作背包容量,把每个子结点t_child都视作一件单位重量为1的物品,但是和背包问题不同的是,这件物品的总价值并不是单位价值乘以总重量,而是重量为m_child的该物品的价值为f(t_child, m_child),这样我就可以像无限背包问题一样,用这样的方法来进行求解!
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
vector<int> e[N];
int a[N];
int dp[N][N], vis[N];
int dfs(int u, int m)
{
if (m < 0)
return 0;
if (dp[u][m])
return dp[u][m] + a[u];
vis[u] = 1;
for (auto v : e[u])
if (!vis[v])
for (int i = m; i >= 0; i--) //以u为根,选i个所获得的最大权值
for (int j = i; j >= 0; j--) //当前选了i-j个获得的最大权值+当前选择当前节点,额外在选j-1个所获得的最大权值.
dp[u][i] = max(dp[u][i], dp[u][i - j] + dfs(v, j - 1));
vis[u] = 0;
return dp[u][m] + a[u];
}
int main()
{
//freopen("in.txt", "r", stdin);
int n, m, u, v;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= n - 1; i++)
{
scanf("%d%d", &u, &v);
e[u].push_back(v);
e[v].push_back(u);
}
int ans = dfs(1, m - 1);
printf("%d\n", ans);
return 0;
}