算法【多重背包】
多重背包是指每一种物品给定数量的限制,进行可能性展开。对于多重背包的枚举优化,本文介绍最常用的二进制分组优化,其他优化如单调队列优化可自行学习。
下面通过题目加深理解。
题目一
测试链接:宝物筛选 - 洛谷
分析:这道题就是典型的多重背包模板,对于多重背包可能性的展开也十分容易想到,对于第i个物品不取或者依次取1个、2个、3个等。这样就可以写出记忆化搜索的版本,代码如下。
#include <iostream>
using namespace std;
int n, W;
int treasure[100][3];
int dp[100][40001];
int f(int index, int weight){
if(index == n){
return 0;
}
if(dp[index][weight] != -1){
return dp[index][weight];
}
int ans = f(index+1, weight);
for(int i = 1;i <= treasure[index][2] && weight - i * treasure[index][1] >= 0;++i){
ans = ans > f(index+1, weight - i * treasure[index][1]) + i * treasure[index][0] ?
ans : f(index+1, weight - i * treasure[index][1]) + i * treasure[index][0];
}
dp[index][weight] = ans;
return ans;
}
int main(void){
scanf("%d%d", &n, &W);
for(int i = 0;i < n;++i){
scanf("%d%d%d", &treasure[i][0], &treasure[i][1], &treasure[i][2]);
}
for(int i = 0;i < n;++i){
for(int j = 0;j <= W;++j){
dp[i][j] = -1;
}
}
printf("%d", f(0, W));
return 0;
}
其中,f方法返回在从下标为index物品开始取,剩余重量为weight的情况下取得的最大值。不过这道题目对于时间复杂度要求比较高,记忆化搜索的版本过不去。所以可以写出严格位置依赖的版本。代码如下。
#include <iostream>
using namespace std;
int n, W;
int treasure[100][3];
int dp[101][40001];
int main(void){
scanf("%d%d", &n, &W);
for(int i = 0;i < n;++i){
scanf("%d%d%d", &treasure[i][0], &treasure[i][1], &treasure[i][2]);
}
for(int i = 0;i <= W;++i){
dp[n][i] = 0;
}
for(int i = 0;i < n;++i){
dp[i][0] = 0;
}
for(int i = n-1;i >= 0;--i){
for(int j = 0;j <= W;++j){
dp[i][j] = dp[i+1][j];
for(int k = 1;k <= treasure[i][2] && j - k * treasure[i][1] >= 0;++k){
dp[i][j] = dp[i][j] > dp[i+1][j-k*treasure[i][1]] + k * treasure[i][0] ?
dp[i][j] : dp[i+1][j-k*treasure[i][1]] + k * treasure[i][0];
}
}
}
printf("%d", dp[0][W]);
return 0;
}
其中,dp数组的含义和记忆化搜索中f方法的含义基本相同,因为严格位置依赖的版本就是根据记忆化搜索写出来的。不过,严格位置依赖的版本依旧过不去。下面还可以写出空间压缩的版本。代码如下。
#include <iostream>
using namespace std;
int n, W;
int treasure[100][3];
int dp[40001];
int main(void){
scanf("%d%d", &n, &W);
for(int i = 0;i < n;++i){
scanf("%d%d%d", &treasure[i][0], &treasure[i][1], &treasure[i][2]);
}
for(int i = 0;i <= W;++i){
dp[i] = 0;
}
for(int i = n-1;i >= 0;--i){
for(int j = W;j >= 0;--j){
for(int k = 1;k <= treasure[i][2] && j - k * treasure[i][1] >= 0;++k){
dp[j] = dp[j] > dp[j-k*treasure[i][1]] + k * treasure[i][0] ?
dp[j] : dp[j-k*treasure[i][1]] + k * treasure[i][0];
}
}
}
printf("%d", dp[W]);
return 0;
}
虽然空间压缩相对于严格位置依赖不会有明显的时间复杂度优化,不过对于这道题目空间压缩还是压着线过了。对于多重背包的真正的优化在题目二体现。
题目二
测试链接:宝物筛选 - 洛谷
分析:对于多重背包一般的优化是将多重背包转化为01背包进行求解。就是将多重背包的件数转化为多个二进制背包的形式,这样可以通过转化出的多个物品不同的取舍方案得到对多重背包取不同件数的体现。比如件数为7,可以从1开始,转化为1,2,4三个背包,对于多重背包7件取多少件,可以通过1,2,4这三个背包取或不取得到。代码如下。
#include <iostream>
using namespace std;
int n, W;
int treasure[2000][2];
int dp[40001];
int main(void){
int v_i, w_i, m_i, index = 0, temp;
scanf("%d%d", &n, &W);
for(int i = 0;i < n;++i){
scanf("%d%d%d", &v_i, &w_i, &m_i);
temp = 1;
while (m_i >= temp)
{
treasure[index][0] = temp * v_i;
treasure[index++][1] = temp * w_i;
m_i -= temp;
temp *= 2;
}
if(m_i != 0){
treasure[index][0] = m_i * v_i;
treasure[index++][1] = m_i * w_i;
}
}
for(int i = 0;i <= W;++i){
dp[i] = 0;
}
for(int i = 0;i < index;++i){
for(int j = W;j >= 0 && j - treasure[i][1] >= 0;--j){
dp[j] = dp[j] > dp[j-treasure[i][1]] + treasure[i][0] ?
dp[j] : dp[j-treasure[i][1]] + treasure[i][0];
}
}
printf("%d", dp[W]);
return 0;
}
题目三
测试链接:樱花 - 洛谷
分析:这道题看似有一个完全背包,但是可以将完全背包转化为多重背包。即如果次数表示无数次,但是要在满足时间条件的情况下,所以求出一个最大次数和原无数次是等价的,这样就转化为了多重背包。代码如下。
#include <iostream>
using namespace std;
int Time, n;
int tree[1000000][2];
int dp[1001];
int main(void){
int hour1, hour2, minute1, minute2;
char ch;
int T_i, C_i, P_i, temp, index = 0;
cin>>hour1>>ch>>minute1>>hour2>>ch>>minute2;
Time = (hour2 - hour1) * 60 + minute2 - minute1;
cin>>n;
for(int i = 0;i < n;++i){
scanf("%d%d%d", &T_i, &C_i, &P_i);
temp = 1;
if(P_i == 0){
P_i = Time / T_i;
}
while (P_i >= temp)
{
tree[index][0] = temp * T_i;
tree[index++][1] = temp * C_i;
P_i -= temp;
temp *= 2;
}
if(P_i > 0){
tree[index][0] = P_i * T_i;
tree[index++][1] = P_i * C_i;
}
}
for(int i = 0;i <= Time;++i){
dp[i] = 0;
}
for(int i = 0;i < index;++i){
for(int j = Time;j >= 0 && j - tree[i][0] >= 0;--j){
dp[j] = dp[j] > dp[j-tree[i][0]] + tree[i][1] ?
dp[j] : dp[j-tree[i][0]] + tree[i][1];
}
}
printf("%d", dp[Time]);
return 0;
}
其中,dp数组表示在下标0~i的物品取,剩余重量为j的情况下取得的最大值。