题目链接:http://poj.org/problem?id=1155
题目大意:给定一棵树,1为根结点表示电视台,有m个叶子节点表示客户,有n-m-1个中间节点表示中转站,每条树边有权值。现在要在电视台播放一场比赛,每个客户愿意花费cost[i]的钱观看,而从电视台到每个客户也都有个费用,并且经过一条边只会产生一个费用。问电视台不亏损的情况最多有几个客户可以看到比赛?1<=n<=1000,1<=m<=n-1;
解题思路:本题解法为树形DP+背包,和Poj1947有点相似,1947也是在树形结构上进行分组背包处理。本题在思考的时候可分两种情况:1、当节点为叶子节点时,每个用户都要支付Money,那当前选一个的价值为Money值,中转站和电视台要计算的是在保证不亏损也就是从用户那获得得价值减去中转费用必须大等于0,那么叶子节点的Money值就是基本信息。2、当节点为中转站或者非叶子节点时,每次从子节点中选择n个并计算收取的Money和中转费用,如果两者想减大等于0就用n来更新答案。怎么转换到分组背包呢?可以这样想,每次从每个子节点中都只能取若干个,不可能重叠着取,那几个节点就是几组背包,最大容量是这点的叶子子孙数量,选几个节点就是选择的容量,价值就是用户给的Money-中转费用。
假设dp[i][j]表示以i为根节点选择j个用户产生的最大价值。
状态转移方程: dp[v][1] = Money[v]; (v为叶子节点)
dp[v][j] = max(dp[v][j],dp[v][j-i] + dp[k][i] - len);(v为非叶子节点,j表示用户个数,i为容量,k为v的子节点,len为边权)
算法复杂度O(sum(num[i],num[s])) (num[i]为某个节点的叶子子孙个数,num[s]为i的子节点的叶子子孙个数)
测试数据:
6 5
5 2 3 3 4 4 5 5 6 6 7
1 1 1 1 1
9 6
3 2 2 3 2 9 3
2 4 2 5 2
3 6 2 7 2 8 2
4 3 3 3 1 1
代码:
#include <stdio.h>
#include <string.h>
#define MAX 3010
#define INF 0xfffffff
#define max(a,b) (a)>(b)?(a):(b)
struct node {
int v,len;
node *next;
}*head[MAX*3],tree[MAX*3];
int ans,sum[MAX],vis[MAX];
int n,m,ptr,dp[MAX][MAX],money[MAX];
void AddEdge(int x,int y,int len) {
tree[ptr].v = y,tree[ptr].len = len;
tree[ptr].next = head[x],head[x] = &tree[ptr++];
tree[ptr].v = x,tree[ptr].len = len;
tree[ptr].next = head[y],head[y] = &tree[ptr++];
}
void Dfs(int v,int len) {
if (vis[v]) return ;
vis[v] = 1,dp[v][0] = 0;
int i,j,k,tot = 0;
node *p = head[v];
while (p != NULL) {
if (!vis[p->v]) {
tot++;
Dfs(p->v,len);
sum[v] += sum[p->v];
}
p = p->next;
}
if (tot == 0) {
//根节点
sum[v] = 1;
dp[v][1] = money[v] - len;
}
else {
p = head[v];
while (p != NULL) {
k = p->v;
len = p->len;
for (j = sum[v]; j >= 1; --j)
for (i = 1; i <= sum[k]; ++i)
if (j >= i && dp[v][j-i] != -INF && dp[k][i] != -INF)
dp[v][j] = max(dp[v][j],dp[v][j-i] + dp[k][i] - len);
p = p->next;
}
}
}
int main()
{
int i,j,k,a,b;
while (scanf("%d%d",&n,&m) != EOF) {
ptr = 1,ans = -INF;
memset(sum,0,sizeof(sum));
memset(vis,0,sizeof(vis));
memset(head,NULL,sizeof(head));
for (i = 0; i <= n; ++i)
for (j = 0; j <= n; ++j)
dp[i][j] = -INF;
for (i = 1; i <= n - m; ++i) {
scanf("%d",&k);
for (j = 1; j <= k; ++j)
scanf("%d%d",&a,&b),AddEdge(i,a,b);
}
for (i = n - m + 1; i <= n; ++i)
scanf("%d",&money[i]);
Dfs(1,0);
for (i = n; i >= 0; --i)
if (dp[1][i] >= 0) {
printf("%d\n",i);
break;
}
}
return 0;
}
本文ZeroClock原创,但可以转载,因为我们是兄弟。