本篇参考自传送门
问题
有N件物品和一个容量为V的背包。第i件物品的费用是w [ i ],价值是v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思想
这个问题就转化为了每一组都可以有多种取法,但是只能取一个物品,也可以不取,则可以设 dp[i,j] 表示前i 组物品容量为 j 时的最大收益
则状态转移方程为
dp[k][j]=max(dp[k−1][j],dp[k−1][j−c[i]]+w[i]∣物品i属于组k)
伪代码
for( 1 - n)//所有的组
for( v - 0)//枚举所有容量
for( 1 - 该组所有物品数量 )
dp[j] = max{dp[j], dp[j - w[i]] + v[i]}
其中要注意 枚举每组数量的循环一定是要在枚举容量的内层,不然会出现一组中多于一个物品被取出
这样枚举就保证了每一个状态下每个组中最多只取一个物品,因为是每个状态下进行 多次比较,看该组内哪个物品价值更高,如果当前在该组内拿出的物品的价值大于之前在该组拿出物品的价值,就进行替换,如果枚举完该组所有物品,都不能使该状态下的价值大于之前的价值,这个组的物品就选择一个不拿。
例题
1、 xinjun与阴阳师
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
int dp[maxn],val[maxn][maxn],w[maxn][maxn];
int num[maxn];
void Solve(int n,int m)
{
memset(dp, 0, sizeof dp);
for(int i = 1; i <= n; i++){
cin>>num[i];
for(int j = 1; j <= num[i]; j++) cin>>val[i][j];
for(int j = 1; j <= num[i]; j++) cin>>w[i][j];
}
for(int i = 1; i <= n; i++){
for(int j = m; j >= 0; j--){
for(int k = 1; k <= num[i]; k++){
if(j >= w[i][k]) dp[j] = max(dp[j],dp[j - w[i][k]] + val[i][k]);
}
}
}
cout<<dp[m]<<endl;
}
int main()
{
int t;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
Solve(n,m);
}
return 0;
}
2、luogu P1757 通天之分组背包
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
int dp[maxn],v[maxn][maxn],w[maxn][maxn],num[maxn];
void Solve(int m,int n)
{
int count = 0;
memset(dp,0,sizeof dp);
for(int i = 1; i <= n; i++){
int a,b,c;
cin>>a>>b>>c;
count++;
num[c]++;
w[c][num[c]] = a;
v[c][num[c]] = b;
}
for(int i = 1; i <= count; i++){
for(int j = m; j >= 0; j--){
for(int k = 1; k <= num[i]; k++){
if(j >= w[i][k]) dp[j] = max(dp[j],dp[j - w[i][k]] + v[i][k]);
}
}
}
cout<<dp[m]<<endl;
}
int main()
{
int m,n;
cin>>m>>n;
Solve(m,n);
return 0;
}