题目大意:
这道题的大意就是有N个节点,1号节点为发射站,2-n-m 是中途的传递站,n-m+1到n为用户,用户收到电视信号就会付出一定的钱,其中每条边有边权,代表传递的花费,
问在不亏本的情况下最多能给多少个用户发送信号。
题目分析:
这道题就是在树上背包,用Dp[i][j]表示以i为根节点管理j个用户所得到的最大收益。
接下来处理细节,怎么知道管理的用户的个数,用一个num数组标记,用户设为1,传递站设为0,在dfs求解子树的过程中进行更新,num[root]=∑num[son];
关于用户付出的金钱,直接用dp[i][0]表示,方便dp的时候使用;
那么dp方程就是:
dp[root][j+k]=max(dp[root][j+k],dp[root][j]+dp[son][k]);这就是一个背包;
我们采用正推的方式更新。
但是我们需要思考一个问题,在dp更新的时候可能会出现一些错误,例如在更新的过程中dp[root][j]可能是已经使用过这棵son子树了,而我们的j是在除当前son子树以外的用户数,所以我们需要用一个临时数组tem提取出来。
最后输出最大的i使得dp[1][i]大于0;
如下代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 3100
#define INF 0x7fffffff
using namespace std;
int n,m;
int dp[N][N],head[N],a,b,num[N],len,k,tem[N];
struct Node
{
int now,next,w;
}edge[N*2];
void dfs(int root,int fa)
{
int p;
for(int i=head[root];i!=-1;i=edge[i].next)
{
p=edge[i].now;
if(p==fa) continue;
dfs(p,root);
for(int j=0;j<=num[root];j++)
tem[j]=dp[root][j];
for(int j=0;j<=num[root];j++)
for(int k=1;k<=num[p];k++)
{
dp[root][j+k]=max(dp[root][j+k],tem[j]+dp[p][k]-edge[i].w);
}
num[root]+=num[p];
}
}
void addedge(int u,int v,int W)
{
edge[len].now=v;
edge[len].next=head[u];
edge[len].w=W;
head[u]=len++;
}
int main()
{
scanf("%d%d",&n,&m);
len=0;
memset(head,-1,sizeof(head));
for(int i=1;i<=n-m;i++)
{
num[i]=0;
scanf("%d",&k);
for(int j=1;j<=k;j++)
{
scanf("%d%d",&a,&b);
addedge(i,a,b);
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][j]=-INF;
for(int i=n-m+1;i<=n;i++)
{
num[i]=1;
scanf("%d",&dp[i][1]);
}
dfs(1,-1);
for(int i=m;i>=0;i--)
{
if(dp[1][i]>=0)
{
printf("%d\n",i);
break;
}
}
//while(1);
return 0;
}