【题解】有线电视网
题目大意:给一颗有边权有点权的树,点权为正,边权为负,求出一个连通块,使得其块内权值之和非负,且包含叶子数最多。(点权只在叶子上)
设计\(dp_{i,j}\)表示第\(i\)个点,选择\(j\)个叶子的最大权值。
那么,枚举每一个点,及其子节点,当递归到底时,\(dp[i][1]=v[i]\).回溯时,看成背包问题,枚举能选用的最大叶子数目,枚举这个枝条上能选的最多叶子数目,将能选的叶子数目看做物品,最大叶子数目看做容量,做\(01\)背包即可。
说实话好久没写树形\(dp\)了,这种带边权的题着实有点不舒服。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3001;
int n,m,tot,head[MAXN<<1];
int v[MAXN],dp[MAXN][MAXN];
int siz[MAXN];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return w==-1?-s:s;
}
struct edge{
int nxt,to,dis;
}e[MAXN*MAXN];
inline void add(int x,int y,int w){
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
e[tot].dis=w;
}
int dfs2(int x){
if(x>n-m){
dp[x][1]=v[x];
return 1;
}
int s=0,t;
for(int i=head[x];i;i=e[i].nxt){
int j=e[i].to;
t=dfs2(j);s+=t;
for(int l=s;l>=1;--l){
for(int k=1;k<=t;++k){
if(l-k>=0)
dp[x][l]=max(dp[x][l],dp[x][l-k]+dp[j][k]-e[i].dis);
}
}
}
return s;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n-m;++i){
int x=read();
for(int j=1;j<=x;++j){
int a=read(),b=read();
add(i,a,b);
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
dp[i][j]=-2222;
for(int i=1;i<=n;++i)dp[i][0];
for(int i=n-m+1;i<=n;++i)v[i]=read();
//dfs(1);
dfs2(1);
for(int i=m;i>=1;--i)
if(dp[1][i]>=0){
cout<<i<<endl;
break;
}
return 0;
}