题目:
树上的动态规划:
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(t, m)而是针对于每一个t,同时求解它的f(t, 0..M),这样的话,我就可以把m视作背包容量,把每个子结点t_child都视作一件单位重量为1的物品,但是和背包问题不同的是,这件物品的总价值并不是单位价值乘以总重量,而是重量为m_child的该物品的价值为f(t_child, m_child),这样我就可以像无限背包问题一样,用这样的方法来进行求解!
可以以后序遍历的方式访问这棵树,这样当计算f(t, 0..M)的时候,我就已经计算出了所有的f(t_child, m_child)的值,如果我将这些值储存在数组中的话,我就不需要再递归计算了!
代码:
#include <iostream>
#include <cstring>
using namespace std;
typedef struct edge{
int to;
int next;
}e;
const int MAXN = 102;
e edge[MAXN*2];
int N, M;
int head[MAXN], value[MAXN];
int NE;
int f[MAXN][MAXN];
void dfs(int p, int u){
for(int i=head[u];i!=-1;i=edge[i].next)
if(p != edge[i].to){
dfs(u, edge[i].to);
for(int m = M;m>1;m--){
for(int m_child = 1;m_child<=m-1;m_child++){
f[u][m] = max(f[u][m], f[u][m-m_child]+f[edge[i].to][m_child]);
}
}
}
}
int main(){
cin>>N>>M;
memset(head, -1, sizeof(head));
memset(f, 0, sizeof(f));
for(int i=0;i<N;i++){
cin>>value[i];
f[i][1] = value[i];
}
NE = 0;
for(int i=0;i<N-1;i++){
int u,v;
cin>>u>>v;
u--; v--;
edge[NE].to = v;
edge[NE].next = head[u];
head[u] = NE++;
edge[NE].to = u;
edge[NE].next = head[v];
head[v] = NE++;
}
dfs(N, 0);
cout<<f[0][M]<<endl;
return 0;
}