P1273 有线电视网 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
书上分组背包一般形式:
vector<int> h[N]; // 存图
void dfs(int u)
{
for(auto i : h[u]) // 枚举物品
{
dfs(i); // 往下接着搜
for(int j = m; j ; --j) //枚举体积
{
for(int k = 1; k <= j; ++j) // 枚举每个子树体积
//转移方程
}
}
}
本题[i][j]含义,表示i节点,选j个用户,能得到的钱的最大值,然后对每个节点做分组背包。
怎么转移
首先,背包的总容量相当于该点为根节点的子树中所有的用户数量(dp[i][j]的 j 不可能超过它连接的所有用户数)。然后,把该节点的每个儿子看成一组,每组中的元素为选一个,选两个...选n个用户。
转移方程 f[i][j]=max(f[i][j],f[i][j-k]+f[v][k]-这条边的花费) i,j不解释了,v表示枚举到这一组(即i的儿子),k表示枚举到这组中的元素:选k个用户
最后输出f[1][i]>=0的i的最大值,所以反向枚举。
本题中决策时不能从最大体积决策, 要从实际含义出发
具体看代码:
#include<bits/stdc++.h>
using namespace std;
const int N =3010;
int f[N][N]; //f[i][j] 表示已i为根节点选了j个用户的最大价值
vector<pair<int,int>> h[N];//存图
int n, m;
int w[N];
int dfs(int u) // dfs返回的时树中用户用户个数
{
if(u > n - m)
{
f[u][1] = w[u];
return 1;
}
int s = 0; // s为树总用户个数, 即背包体积
for(auto [x, y] : h[u])
{
int v = dfs(x); // 子树体积
s+=v;
//分组背包
for(int j = s; j ; --j)
{
for(int k = 1; k <= v && k <= j; ++k)
{
f[u][j] = max(f[u][j], f[x][k] + f[u][j - k] - y);
}
}
}
return s;
}
int main()
{
memset(f, -0x3f, sizeof f);
cin >>n >>m;
for(int i = 1; i <= n; ++i) f[i][0] = 0;
for(int i = 1; i <= n - m; ++i)
{
int k;
cin >>k;
while(k--)
{
int a, c;
cin >>a >>c;
h[i].push_back({a, c});
}
}
for(int i = n - m + 1; i <= n; ++i) cin >>w[i];
dfs(1);
int ans = 0;
for(int i = m; i >= 0; --i) // 反向输出
{
if(f[1][i] >= 0)
{
cout<<i<<endl;
break;
}
}
}