问题大意:给出一棵树,根节点为1,每个点有毒素和收获。要求毒素不超过给定值的情况下使收获最大。一个点的父亲节点被选取后这个点才能被选取。
首先弄出dfs序,也记录下每个点其子树及自身的大小。每个点都能够被选或不选,如果选了才会考虑它子树。
设 f[i][j] 表示dfs序上第i位上的点在其子树及自身上选取了毒素和为j的点所能获得的最大收益。(下面x指dfs序上第i位代表的点)
如果 j<cost[x] , f[i][j]=f[i+size[x]][j] 。代表这个点及其子树都不能选( +size[x] 代表在dfs序上跳过这个点的子树)
如果 j≥cost[x] , f[i][j]=max(f[i+1][j−c[x]]+v[x],f[i+size[x]][j]) 。
注意是否会出现负收益,并选择是否需要再与0比较。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std ;
bool Read ( int &x, char c = getchar(), bool flag = false ) {
for ( x = 0 ; !isdigit(c) ; c = getchar() ) if ( c == EOF ) return false ; else if ( c == '-' ) flag = true ;
for ( ; isdigit(c) ; c = getchar() ) x = 10*x + c - '0' ; if ( flag ) x = -x ;
return true ;
}
const int maxn = 5010, maxm = maxn<<1, zhf = 1<<28 ;
int ans, n, m, e, be[maxn], to[maxm], nxt[maxm], v[maxn], c[maxn], dfn[maxn], rdn[maxn], clk, size[maxn], f[maxn][maxn] ;
void add ( int x, int y ) {
to[++e] = y ;
nxt[e] = be[x] ;
be[x] = e ;
}
void dfs ( int x, int fa ) {
( dfn[x] = ++clk )[rdn] = x ;
size[x] = 1 ;
int i, u ;
for ( i = be[x] ; i ; i = nxt[i] ) {
u = to[i] ;
if ( u == fa ) continue ;
dfs(u,x) ;
size[x] += size[u] ;
}
}
int main() {
int i, j, k, x, y, z ;
Read(n) ; Read(m) ;
for ( i = 1 ; i <= n ; i ++ ) {
Read(v[i]) ;
Read(c[i]) ;
}
for ( i = 1 ; i < n ; i ++ ) {
Read(x) ; Read(y) ;
add ( x, y ) ;
add ( y, x ) ;
}
dfs(1,1) ;
for ( i = n ; i ; i -- ) {
x = rdn[i] ;
for ( j = 0 ; j <= m ; j ++ )
if ( j < c[x] ) f[i][j] = max ( f[i+size[x]][j], 0 ) ;
else f[i][j] = max ( max( f[i+1][j-c[x]]+v[x], f[i+size[x]][j] ), 0 ) ;
}
printf ( "%d\n", f[1][m] ) ;
return 0 ;
}
感谢Wearry在模拟赛中提到这个知识点。