树上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;
}