http://poj.org/problem?id=1155
题意:某电台要广播一场比赛,该电台网络是由N个网点组成的一棵树,其中M个点为客户端,其余点为转发站。客户端i愿支付的钱为pay[i],每一条边需要的花费固定,问电台在保证不亏损的情况下,最多能使多少个客户端接收到信息?广播台所在的节点编号为1。
思路:树形dp。 用dp[root][d] 表示以root为根的子树中,保留d个用户的最大利润,由于每个结点的孩子的数目并不是最多只有两个,因此在求dp[root][d]的时候要进行一次横向的dp,即对root的所有孩子进行一个背包,在具体实现的时候我们可以一次求出root的所有可能d的最大利润(在背包dp的时候一起求出)。
需要注意的几点:1、树最好用邻接矩阵存储,这样可以节约寻找root孩子的时间;
2、用一个num[ ]数组存储每个root状态数, 即d的最大值,这样比每次都从M开始求用时会少,避免超时。
代码:
#include<stdio.h>
#include<string.h>
#define INF 100000000
#define MAX(a,b) (a) > (b) ? (a) : (b)
int N ,M ;
int pay[3010] ;
int dp[3010][3010] ;
struct Node{
int d ;
int next ;
int n ;
}edge[3010] ;
int root[3010] ;
int cnt ;
int num[3010] ; //存放每个结点的状态种数
void dfs(int u){
if(u > N-M){
num[u] = 1 ;
dp[u][1] = pay[u] ;
return ;
}
dp[u][0] = 0 ;
for(int i=root[u]; i!=-1; i=edge[i].next){
int v = edge[i].n ;
int d = edge[i].d ;
dfs(v) ;
num[u] += num[v] ;
for(int j=num[u];j>=0;j--){ //孩子的背包
for(int k=1;j-k>=0 && k<=num[v];k++){
dp[u][j] = MAX(dp[u][j] , dp[u][j-k]+dp[v][k]-d );
}
}
}
}
void add(int u , int v, int d){
edge[cnt].d = d ;
edge[cnt].n = v ;
edge[cnt].next = root[u] ;
root[u] = cnt ++ ;
}
int main(){
int a , b ,c ,i, j ;
while(scanf("%d %d",&N,&M) == 2){
memset(root , -1 ,sizeof(root) );
cnt = 0 ;
for(i=1;i<=N-M;i++){
scanf("%d",&a);
for(j=0;j<a;j++){
scanf("%d %d",&b,&c);
add(i,b,c);
}
}
for(i=N-M+1;i<=N;i++){
scanf("%d",&pay[i]);
}
for(int i=1;i<=N;i++){
for(int j=0;j<=M;j++)
dp[i][j] = -INF ;
}
memset(num , 0 ,sizeof(num));
dfs(1) ;
for(int i=num[1];i>=0;i--){
if(dp[1][i] >= 0){
printf("%d\n",i);
break ;
}
}
}
return 0 ;
}