树中的每个结点都有权重(Wi, Wi>0),编号从1~N,要求选出包含编号为1的共M个结点,使得总和最大。
基本思想:树中的动态规划。
以下转载自hihocoder:
(1) f(t, m)表示,在以t为根的一棵树中,选出包含根节点t的m个连通的结点,能够获得的最高的评分,本题答案就是f(1, M)
(2) 转化为背包问题。针对于每一个t,同时求解它的f(t, 0..M),这样的话,可以把m视作背包容量,把每个子结点t_child都视作一件单位重量为1的物品,但是和背包问题不同的是,这件物品的总价值并不是单位价值乘以总重量,而是重量为m_child的该物品的价值为f(t_child, m_child),这样我就可以像无限背包问题一样,用这样的方法来进行求解。
(3) 采用后序遍历。这样在计算f(t,m)时,f(t_child, m_child)已经知道了。
(4) 每当遍历过一个child结点,用该结点的f(t_child, m_child )值来替换其父节点的一部分,可以保证权重值相加没有重复。
另:看过一个大神的代码,发现他存树的方式很特别。没有用传统的指针,而是用数组。每个结点对应于第一条子边(一维数组)。该边有另一头的结点和其兄弟边。我模仿他的方法,用数组存树这个结构。首先第i个结点可以直接找到他的第一个孩子child[i],每个结点存储它相邻的一个兄弟结点brother[i]。则比如结点1有孩子结点234,则child[1...4]={2,0,0,0},brother[1...4]={0,3,4,0}。由于本题编号固定,用下标存取效率快。
#include <stdio.h>
#include <string.h>
#define MAX(a,b) ((a)>(b))?(a):(b);
int f[101][101];// 0<=M<=N<=100 f[N][M]
int child[101];//the first child node
int brother[101];//brother node
int N=0;
int M=0;
void printArray(){
printf("test:\n");
for(int i=0; i<=N; i++ ){
printf("%d ",i);
}
for(int i=0; i<=N; i++ ){
printf("%d ",child[i]);
}
for(int i=0; i<=N; i++ ){
printf("%d ",brother[i]);
}
}
void calculateF( int node, int childNode ){
for( int m = M; m>=2; m-- ){
for( int child_m = 1; child_m<m; child_m++ ){
f[node][m] = MAX( f[node][m], f[node][m-child_m]+f[childNode][child_m] );
}
}
}
void dfs(int node){
int childNode = child[node];
while( childNode ){
dfs(childNode);
calculateF( node, childNode );
childNode = brother[childNode];
}
}
int main(){
scanf("%d%d", &N, &M);
memset(f,0,sizeof(f));
memset(child,0,sizeof(child));
memset(brother,0,sizeof(brother));
for(int i=1; i<=N; i++ )//N个结点的值
scanf( "%d",&f[i][1] );
int n1=0,n2=0;
for(int i=1; i<N; i++ ){//N-1条边
scanf("%d%d",&n1,&n2);
if( child[n1]==0 )
child[n1]=n2;
else{
n1=child[n1];
while( brother[n1]!=0 )
n1= brother[n1];
brother[n1] = n2;
}
}
//test
//printArray();
dfs(1);
printf("%d\n",f[1][M]);
}