01背包
f[i,j]表示前i件物品,体积不超过j的最大价值
每件物品一个
转移方程
由于i状态转移只用到f[i-1],故优化掉第一维,具体来说计算f[j]的时候要有上层f[j-v],由于去掉第一维度后本身就是上一层的值,所以需要逆序遍历,保证j-v是上层的j-v.
/*
* @Author: ACCXavier
* @Date: 2021-04-25 19:57:50
* @LastEditTime: 2021-05-24 11:54:01
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:https://www.acwing.com/problem/content/2/
* @keywords: 01背包
*/
#include <iostream>
using namespace std;
const int N = 2020;
int f[N];//状态
int main()
{
int n,m;
cin>>n>>m;int v,w;
for(int i = 0;i < n; ++ i){
cin>>v>>w;
for(int j = m; j >= v; --j)f[j] = max(f[j],f[j-v]+w);
}
cout<<f[m]<<endl;
return 0;
}
完全背包
f[i,j]表示前i件物品,体积不超过j的最大价值
每件物品可拿无限
转移方程
注意这里物品无限,所以拿到体积j拿不下为止,所以f[i,j]和f[i,j-v]的末项都是一样的,所以可以合并
由于i状态转移只用到f[i]层,故优化掉第一维,具体来说计算f[j]的时候要有本层f[j-v], (注意状态方程里面是f[i,j-v]+w) 所以要保证计算i,j前i,j-v要被算过,正序遍历.
/*
* @Author: ACCXavier
* @Date: 2021-04-26 14:48:50
* @LastEditTime: 2021-04-26 15:09:45
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:https://www.acwing.com/problem/content/3/
* @keywords: 完全背包问题
*/
#include <iostream>
using namespace std;
const int N = 1010;
int f[N];
int main() {
int n,m,v,w;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin>>v>>w;
for (int j = v; j <= m; j++) {
f[j] = max(f[j],f[j-v]+w);
}
}
cout<<f[m]<<endl;
return 0;
}
多重背包
f[i,j]表示前i件物品,体积不超过j的最大价值
每件物品可拿s[i]个
转移方程
其中k表示s[i]中拿k个
这里是不能像完全背包那样优化,因为
f[i,j-v]表示前i件物品,体积不超过j-v的拿法,拿第i个最多拿s[i]个,故每一个f[i.j-v]都有s[i]项,对齐后后面会多一项,不能由f[i,j-v]和f[i-1,j-(s+1)v]+sw)直接求出f[i,j]最大值.
- 比如f[i,j-v]是140,f[i-1,j-(s+1)v]+sw)也是140,就求不出f[i-1,j-v] ,f[i-1,j-2v]+ w,…,f[i-1,j-s[i]v]+(s[i]-1)w的最大值,也就求不出f[i,j]的值
二进制优化
假设A物品由888个,可以拆成1,2,4,8,16,32,64,128,256,377,可以保证这些数字可以凑出1~888之间任何数字,故只需要把888个A物品打包为这些个数A物品的包,看成01背包按包拿即可.由于物品n个被拆成logn个,时间复杂度为O(nmlogn),m是物品大小
/*
* @Author: ACCXavier
* @Date: 2021-04-27 16:16:10
* @LastEditTime: 2021-05-24 12:22:10
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:https://www.acwing.com/problem/content/5/
* @keywords: 多重背包II 二进制优化
*/
#include <iostream>
using namespace std;
const int M = 2010;
int f[M];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
int v, w, s;
cin >> v >> w >> s;//每种物品s个
for (int k = 1; k <= s; k *= 2) {//拆分包的大小为1,2,4,...
//拆成二进制次幂之后比如 k = 4,4个物品一打包
//01背包更新单个物品状态方程
for (int j = m; j >= k * v; --j) {//j>=单个物品体积(其实是4个打包)
f[j] = max(f[j], f[j - k * v] + k * w);//01背包
}
s -= k;//物品数量更新,888-1-2-4-...
}
if (s)//多出来一部分,上面就是888中的377
for (int j = m; j >= s * v; j--)
f[j] = max(f[j], f[j - s * v] + s * w);
}
cout << f[m] << endl;
return 0;
}
单调队列优化
什么时候我把代码看懂了再更新
思路是
分组背包
f[i,j]表示前i组物品,体积不超过j的最大价值
每组物品可拿某一个,每组s[i]种物品
和01背包相比多一层组内遍历,看选不选改组和组内选哪一个
/*
* @Author: ACCXavier
* @Date: 2021-04-28 22:48:06
* @LastEditTime: 2021-05-23 21:24:43
* Bilibili:https://space.bilibili.com/7469540
* 题目地址:https://www.acwing.com/problem/content/description/9/
* @keywords: 分组背包
*/
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 110;
int f[N], v[N][N], w[N][N], s[N];
int n, m;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> s[i];
for (int j = 1; j <= s[i]; ++j) {//@1从1开始,第i组物品的第j种
cin >> v[i][j] >> w[i][j];
}
}
for (int i = 1; i <= n; ++i) {
for (int j = m; j >= 0; --j) {
for (int k = 0; k <= s[i]; ++k) {//能够遍历到=s[i]是因为@1处下标从1开始,第s[i]种的下标就是s[i],如果@1处从0开始则这里k不能取到s[i]//另外,不拿第i组等价于f[j] = f[j],所以这里k取1和0都可以 (但是二维就必须k从0开始)
if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);//@v[i][k] 第i组的第k种 不是v[i][j]
}
}
}
cout << f[m];
return 0;
}