分组背包模板题
总共 组,每组最多取一个物品,实际上就是一个物品总数为 的背包。
for(int i=1;i<=s;i++){//枚举组
for(int j=1;j<=n[i];j++){//枚举每组的元素
for(int k=1;k<=m;k++){//枚举背包大小
f[i][k]=max(f[i][k],f[i-1][k]);
if(k>=w[i][j])
f[i][k]=max(f[i][k],f[i-1][k-w[i][j]]+v[i][j]);
}
}
}仔细观察,可以发现代码和 01 背包十分相似,只不过外面套了一个枚举组的循环。
但是还有一点和 01 背包有区别,就是第 行,还需要和自己比较一次,这是因为我们之前枚举同组的其他元素时,可能已经给 赋值了,所以不能忽略,需要参与比较。
接下来我们考虑如何优化空间。
for(int i=1;i<=s;i++){
for(int k=m;k>=1;k--){
for(int j=1;j<=n[i];j++){
if(k>=w[i][j]) f[k]=max(f[k],f[k-w[i][j]]+v[i][j]);
}
}
}与上面的代码相比,我们修改了两个部分:
- 改为从大到小。 这是为了让 求到的是上一层的、未修改过的值,与 01 背包的空间优化相似。
- 将枚举 的循环反转位置。 我们发现,如果把 放在最内层的话, 每增加 , 的内容就要翻新一遍,这样我们求的 就已经修改过了,并不是上一层的原值了,所以我们把 放在最里面。
时间复杂度 。 空间复杂度 。
下面放上完整代码:
优化前 Code
#include<bits/stdc++.h>
using namespace std;
int n[110],m,t,s;
int w[110][1010],v[110][1010];
int f[110][1010];
int main(){
cin>>m>>t;
for(int i=1;i<=t;i++){
int a,b,c;
cin>>a>>b>>c;
n[c]++;
w[c][n[c]]=a;
v[c][n[c]]=b;
s=max(s,c);
}
for(int i=1;i<=s;i++){
for(int j=1;j<=n[i];j++){
for(int k=1;k<=m;k++){
f[i][k]=max(f[i][k],f[i-1][k]);
if(k>=w[i][j])
f[i][k]=max(f[i][k],f[i-1][k-w[i][j]]+v[i][j]);
}
}
}
cout<<f[s][m];
return 0;
}优化后 Code
#include<bits/stdc++.h>
using namespace std;
int n[110],m,t,s;
int w[110][1010],v[110][1010];
int f[1010];
int main(){
cin>>m>>t;
for(int i=1;i<=t;i++){
int a,b,c;
cin>>a>>b>>c;
n[c]++;
w[c][n[c]]=a;
v[c][n[c]]=b;
s=max(s,c);
}
for(int i=1;i<=s;i++){
for(int k=m;k>=1;k--){
for(int j=1;j<=n[i];j++){
if(k>=w[i][j]) f[k]=max(f[k],f[k-w[i][j]]+v[i][j]);
}
}
}
cout<<f[m];
return 0;
}树上 DP + 分组背包
树形 DP + 分组背包解法思路:
- 表示以 为根的子树中,容量为 时的最大价值
- 对于每个子树,将其看作一个物品组,使用分组背包的思路合并
伪代码:
void dfs(int u) {
f[u][0] = 0; // 初始化容量为0时的价值为0
for (auto v : children[u]) { // 遍历子节点
dfs(v);
for (int j = m; j >= 0; j--) { // 从大到小遍历容量
for (int k = 0; k <= j; k++) { // 遍历子节点的容量分配
f[u][j] = max(f[u][j], f[u][j - k] + f[v][k]);
}
}
}
}