【题解】有线电视网

\(Question\)

题目大意:给一颗有边权有点权的树,点权为正,边权为负,求出一个连通块,使得其块内权值之和非负,且包含叶子数最多。(点权只在叶子上)

设计\(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;
}
posted @ 2020-03-30 22:08  Refined_heart  阅读(110)  评论(0编辑  收藏  举报