树上dp+分组背包类问题

今天也是无意间看到了一个树上dp+分组背包类的问题,有些难度的,不好想的嘞,终究还是一个蒟蒻罢了,唔无捂误

话不多说直接看题

P1273 有线电视网 

在说明这道题之前,还有一个要提醒的地方就是,树上dp,他既简单,又没那么简单,他只是比一般的dp好想一点,但是难度还是比较高的,我们树上dp每个点的值一定都有其子树有关

题意:就是告诉你有n个端点,m个用户,然后每个端点之间都有一个开销,然后每个用户都会提供一部分的资金,问你最多能够让多少个用户能够看到这场球赛

思路:很明显的树上dp+分组背包 ,我们设dp[ i ] [ j ]表示的是 i 结点包括 j 个用户所能获得的最大利润,我们只需要确保利润能够dp[ 1 ] [ i ]>=0,最后就输出 i 即可

我们如何判断是分组背包呢,首先,背包的总容量相当于该点为根节点的子树中所有的用户数量(dp[i][j]的 j 不可能超过它连接的所有用户数)。然后,把该节点的每个儿子看成一组,每组中的元素为选1个,选2个...选n个用户。遍历这个结点所能选的最大的结点数

转移方程 dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[v][k]-这条边的花费) i,j不解释了,v表示枚举到这一组(即i的儿子),k表示枚举到这组中的元素:选k个用户。

因此就可以写出代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int q;
int u,w;
int dp[3005][3005];
int val[3005];
int ans[3005][3005];//用来统计i点到j点的花费 
vector<int>e[3005]; 

int dfs(int v)
{
	if(v>=n-m+1)
	{
		dp[v][1]=val[v];
		return 1;//返回子树大小 
	}
	int sum=0;//表示这个数的大小;
	int cnt;//用来统计字数大小
	for(int u:e[v])
	{
		cnt=dfs(u);
		sum+=cnt;
		for(int j=m;j>=1;j--)
		{
			for(int k=1;k<=cnt;k++)
			{
				dp[v][j]=max(dp[v][j],dp[v][j-k]+dp[u][k]-ans[v][u]);
			}
		}
	} 
	return sum;
}

signed main()
{
	cin>>n>>m;
	memset(dp,-0x3f3f3f3f,sizeof(dp));
	for(int v=1;v<=n-m;v++)
	{
		cin>>q;
		for(int i=1;i<=q;i++)
		{
			cin>>u>>w;
			e[v].push_back(u);
			ans[v][u]=w;
		}
	}
	for(int i=n-m+1;i<=n;i++)
	{
		cin>>val[i];
	}
	for(int i=1;i<=n;i++)
	dp[i][0]=0;
	dfs(1);
	for(int i=m;i>=1;i--)
	{
		if(dp[1][i]>=0)
		{
			cout<<i<<"\n";
			break;
		}
	}
	return 0;
} 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值