经典0-1背包问题
题目简述:
n个牛牛吃m个糖果,每个牛牛吃ai个,k对牛牛绑定必须一起吃或不吃,每个牛牛只会出现在一对绑定中(不会重婚) 求能够吃到糖果的牛牛的最大数量。
很显然,我们把配对了的两只牛牛看作一个权值为2的物品,所需容积为二者之和;把单身的牛牛看作权值为1的物品,所需容积就是其本身所需。 那么,这就是个典型的 “0-1背包问题”。
算法分析:
0-1背包问题: n个物品,每个物品的权值为val[i],所需容量为volume[i],背包容积为m。每个物品要么装要么不装,求背包中物品权值总和的最大值。
核心代码就两个for循环:
for (int i = 0; i < n; i++) {
for (int j = m; j >= volume[i]; j--) {
dp[j] = max(dp[j], dp[j - volume[i]] + val[i]);
}
}
return dp[m];
解释一下为什么要从后往前更新,因为每次更新时需要借助 j 值更小的更新前的记录,即 dp_after[j] = max(dp_before[j], dp_before[j - volume[i]] + val[i])。
所以,若从前往后更新,则右边的 dp_before[j - volume[i]] + val[i]) 会变成dp_after[j - volume[i]] + val[i]),因为更新 j 的时候 j-volume[i] 已经更新过了。
反之,若从后往前更新,则更新 j 的时候 j-volume[i] 肯定没有更新过。
C++代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int No7_BeiBao(int m, vector<int>&a, vector<pair<int, int>>&couple) {
int n = a.size(), k = couple.size(); //couple中的编号是从1到n
vector<int>val(k), volume(k);
vector<bool>used(n, false);
// k对可以看成 k个物品
for (int i = 0; i < k; i++) {
val[i] = 2;
volume[i] = a[couple[i].first - 1] + a[couple[i].second - 1];
used[couple[i].first - 1] = used[couple[i].second - 1] = true;
}
// 剩下单身的每只牛牛都是一个物品
for (int i = 0; i < n; i++) {
if (used[i])continue;
val.push_back(1);
volume.push_back(a[i]);
}
n = val.size(); // n个物品,背包容量是m
vector<int>dp(m + 1, 0);
for (int i = 0; i < n; i++) {
for (int j = m; j >= volume[i]; j--) {
dp[j] = max(dp[j], dp[j - volume[i]] + val[i]);
}
}
return dp[m];
}
int main(){
int n,m,k;
cin>>n>>m;
vector<int>a(n);
for(int i=0;i<n;i++)
cin>>a[i];
cin>>k;
vector<pair<int, int>>couple(k);
for(int i=0;i<k;i++)
cin>>couple[i].first>>couple[i].second;
cout<<No7_BeiBao(m, a, couple)<<'\n';
return 0;
}