背包系列已更新
目录
核心dp方程,dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+b[i]) //i为物品数量,j为空间,a[i]为物品占的空间,b[i]为物品价
二,填满背包(只有这个拿了且能把背包空间装满才拿,不然不拿)
一,01背包(对于一个物品,你要么全拿,要么不拿)
核心dp方程,dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+b[i]) //i为物品数量,j为空间,a[i]为物品占的空间,b[i]为物品价
for (int i = 1; i <= n; ++i)for (int j = 0; j <= m; ++j)
{
if (j - a[i] >= 0)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - a[i]] + b[i]);
else dp[i][j] = dp[i - 1][j]; //当然,写上述方程需要你先装得下物品i再说,不行只能继承前面
}
dp思想是我拿i个,那我只需要假设前面i-1个已经拿了最优,则拿第i个时,我判断,如果拿第i个,那么前面剩余空间为[j-a[i]],那么dp[i-1][j-a[i]]+b[i]就是前i-1个,[j-a[i]]的空间加上第i个的价值比上我不拿第i个的价值,取大值
明确后我们就可以从头写到尾
1, 二维 ac代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1100;
//数组放外面默认初始为0
int dp[N][N];
int a[N]; //消耗猫粮
int b[N];//获得豆子
int main() {
int m, n; //m为猫粮(即我的空间,n为房间,即物品数量
while (cin >> m >> n&&(n!=-1||m!=-1)) {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; ++i)cin >> b[i] >> a[i];
for (int i = 1; i <= n; ++i)for (int j = 0; j <= m; ++j)//表示选择第i个物品时,此时有j空间
{ //j初始化为0,因为可能0空间背包也能装大小(占0空间大小的物品(滑稽))
if (j - a[i] >= 0)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - a[i]] + b[i]);
else dp[i][j] = dp[i - 1][j]; //当然,写上述方程需要你先装得下物品i再说,不行只能继承前面
}
cout << dp[n][m] << endl;
}
return 0;
}
2,优化压缩一维
显然,当我N取1e6或者更大时,二维数组就爆了,而且,我们发现dp转移方程,都是i-1是不是有点多余,于是我们可以选择去除这个i,只保留j就好
核心
for (int i = 1; i <= n; ++i)for (int j = m; j>=a[i]; --j) {//这里i放前面,j放后面,且j从大循环到小,保证一个i只拿一次,j从大到小,就保证本次j空间存入i,后面大空间不会再存一次(因为我从大空间到小空间),导致重复使用
dp[j] = max(dp[j], dp[j - a[i]] + b[i]); //j取到a[i] ,一是防止前面什么0空间还能拿的情况出现,二是你j小于a[i]了,也还是dp[j],不会更新,不用去浪费时间循环 //这样我dp[j]更新的都是上一组拿i-1个的,不会重复
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1100;
//数组放外面默认初始为0
int dp[N];
int a[N]; //消耗猫粮
int b[N];//获得豆子
int main() {
int m, n; //m为猫粮(即我的空间,n为房间,即物品数量
while (cin >> m >> n&&(n!=-1||m!=-1)) {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; ++i)cin >> b[i] >> a[i];
for (int i = 1; i <= n; ++i)for (int j = m; j>=a[i]; --j) {//这里i放前面,j放后面,且j从大循环到小,保证一个i只拿一次,j从大到小,就保证本次j空间存入i,后面大空间不会再存一次(因为我从大空间到小空间),导致重复使用
dp[j] = max(dp[j], dp[j - a[i]] + b[i]); //j取到a[i] ,一是防止前面什么0空间还能拿的情况出现,二是你j小于a[i]了,也还是dp[j],不会更新,不用去浪费时间循环 //这样我dp[j]更新的都是上一组拿i-1个的,不会重复
}
cout << dp[m] << endl;
}
return 0;
}
二,填满背包(只有这个拿了且能把背包空间装满才拿,不然不拿)
其实跟01背包没有很大区别,我们在dp转移加一个判断就好了,只要前面的dp[i-1][j]小于0,我就不装物品,因为他不是填满的。只有满的我才装
1,二维基础
AC代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1100;
//数组放外面默认初始为0
int dp[N][N];
int a[N]; //消耗猫粮
int b[N];//获得豆子
int main() {
int m, n; //m为猫粮(即我的空间,n为房间,即物品数量
while (cin >> m >> n&&(n!=-1||m!=-1)) {
memset(dp, -1, sizeof(dp)); //初始值赋值-1,设定他们没有填满
dp[0][0] = 0;//当然,0空间的背包肯定是满的,这样就作为起点
for (int i = 1; i <= n; ++i)cin >> b[i] >> a[i];
for (int i = 1; i <= n; ++i)for (int j = 0; j <= m; ++j) {
if (j - a[i] >= 0) {//首先空间要装的下,这是前提,否则你越负数空间访问得到的就不是正确答案了
if (dp[i - 1][j - a[i]] >= 0)dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - a[i]] + b[i]);//只有前面dp为0或者大于0,我才赋值
else dp[i][j] = dp[i - 1][j];//这里补一句,是因为,可能进了第一个if,但是第二个if又进不去,然后进来第一个if后面的else就不会去了,导致没有继承前面的dp[i][j] = dp[i - 1][j];
} //所以写二维真的浪费空间,浪费时间,浪费脑力,好好学一维吧
else dp[i][j] = dp[i - 1][j];
}
cout << dp[n][m] << endl;//如果没有被赋值到,那么他初始化就是-1,有就是答案
}
return 0;
}
2,一维优化(还是一样的道理,简洁又高效)
#define _CRT_SECURE_NO_WARNINGS 1
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1100;
//数组放外面默认初始为0
int dp[N];
int a[N]; //消耗猫粮
int b[N];//获得豆子
int main() {
int m, n; //m为猫粮(即我的空间,n为房间,即物品数量
while (cin >> m >> n&&(n!=-1||m!=-1)) {
memset(dp, -1, sizeof(dp)); //初始值赋值-1,设定他们没有填满
dp[0] = 0;//当然,0空间的背包肯定是满的,这样就作为起点
for (int i = 1; i <= n; ++i)cin >> b[i] >> a[i];
for (int i = 1; i <= n; ++i)for (int j = m; j >= a[i]; --j) { //j的范围大于等于a[i]就好,因为你小了肯定不用更新(一维又不需要继承前面i-1的数据)
if (dp[j - a[i]] >= 0)dp[j] = max(dp[j], dp[j - a[i]] + b[i]);//可以就更新,不行不用管
}
cout << dp[m] << endl;
}
return 0;
}