背包系列已更新
目录
方法一,一种朴素的想法,其实k个分出来不就是01背包吗,那我们写的时候再一个循环遍历k个就好了
方法二,其实,每个数都可以由二进制数表示,那么我们可以用二进制优化k,就不用去遍历k遍
二,多种物品分成k组,每组只能取一种的多重背包(也叫分组背包)
多重背包有俩种,一种是每种物品有k个,一种是多种物品分成k组,每组只能取一种
一,每种物品有k个的多重背包
方法一,一种朴素的想法,其实k个分出来不就是01背包吗,那我们写的时候再一个循环遍历k个就好了
Sample Input
1 8 2 2 100 4 4 100 2
Sample Output
400
相信背包思想你以熟悉,接下来我们都用一维压缩来写
AC代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 110;
int s[N];//存放每种物品有几个,s[i]表示第i种物品有s[i]个
int dp[N];
int p[N];//价格(即背包空间
int w[N];//重量(即价值
int main() {
int c;
cin >> c;
while (c--) {
memset(dp, 0, sizeof(dp));//dp切记要每次都初始化
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; ++i)cin >> p[i] >> w[i] >> s[i];
//j逆序还是防止重复放入,一维压缩要注意 //k注意条件,首先当然是k不大于s[i]个,然后是j-k * p[i]不小于0,不然空间负数会出意外
for (int i = 1; i <= m; ++i)for (int j = n; j >= p[i]; --j)for (int k = 0; k <= s[i] && k * p[i] <= j; ++k) {//跟01背包写法几乎一致,但是多了个k个的循环
dp[j] = max(dp[j], dp[j - k * p[i]] + k * w[i]);
}
cout << dp[n]<<endl;
}
return 0;
}
方法二,其实,每个数都可以由二进制数表示,那么我们可以用二进制优化k,就不用去遍历k遍
如70可以表示为32+16+8+4+2+1+7,这几个数可以组成1--63任意数(看成二进制就很清楚)
那么我们把有63个的物品分成上面6种数值的物品,无论最优是哪个数字,我都能通过DP得到
这样时间复杂度从n^3变成(n^2)*logs(二进制是指数级降次,从n压缩为log)
//注意,习惯开小数组的,因为二进制优化,这里可能N小空间存不下(会把一个分成很多个),数组开大一点
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 110;
int dp[N];
int p[N*N];//价格(即背包空间 //因为二进制优化,这里可能N小空间存不下(会把一个分成很多个),数组开大一点
int w[N*N];//重量(即价值
int main() {
int c;
cin >> c;
while (c--) {
memset(dp, 0, sizeof(dp));//dp切记要每次都初始化
int n, m;
cin >> n >> m;
int a,b,s;//a,b,s暂时存放数据
int ans = 0;//表示分开后物品有几个
for (int i = 1; i <= m; ++i) {
cin >> a >> b >> s;
int k = 1;
while (k <= s) {
ans++;
s -= k;//拿走k份,c自然要减,不然最后可以表示的最大数肯定不止原来的c了
p[ans] = a*k; w[ans] = b*k;//*k表示重新组成后的物品的价格与重量
k <<= 1;//二进制左移,等于*2
}
if (s > 0) { //如果c有剩,剩下的存为一组
ans++;
p[ans] = a * s; w[ans] = b * s;
}
} //最后分完有ans个物品,已经变成01背包了
for (int i = 1; i <= ans; ++i)
for (int j = n; j >= p[i]; --j)
dp[j] = max(dp[j], dp[j - p[i]] + w[i]);
cout << dp[n]<<endl;
}
return 0;
}
二,多种物品分成k组,每组只能取一种的多重背包(也叫分组背包)
其实基础还是01背包,然后多了一个循环来遍历每一组的物品
洛谷p1757
我们只需要每次对第i组遍历j空间下存入这一组哪个最优,全部填表,最后自然在dp转移中最优
AC代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1100;
int dp[N];
int zu[N];//zu[i]代表第i组里面的物品数量
int w[N][N];//w[i][k]表示第i组第k个物品的重量
int p[N][N];//p则表示......的价值
int main() {
int n, m;
cin >> m >> n;
int a, b, c;
int ans = 0;
for (int i = 1; i <= n; ++i) {
cin >> a >> b >> c;
ans = max(ans, c);//记录有几组,方便后面dp
zu[c]++;
w[c][zu[c]] = a;//zu[c]表示是这一组第几个物品
p[c][zu[c]] = b;
} //j还是从大到小,还是防止重复存入,这里因为物品信息在内循环,所以j临界到0,但是循环内部就要记得判断了
for (int i = 1; i <= ans; ++i)for (int j = m; j >= 0; --j)for (int k = 1; k <= zu[i]; ++k) {
if (j - w[i][k] >= 0)dp[j] = max(dp[j], dp[j - w[i][k]] + p[i][k]);
}
cout << dp[m] << endl;
return 0;
}