01背包问题
参考:
[https://blog.youkuaiyun.com/qq_38410730/article/details/81667885]
[https://www.cnblogs.com/arsenalfaninecnu/p/8945548.html])
什么是01背包问题?
01背包问题描述:有编号分别为a,b,c,d的N=4件物品,它们的重量w分别是2,3,4,5,它们的价值v分别是3,4,5,6,每件物品数量只有一个,现在给你个承重为M=8的背包,如何让背包里装入的物品具有最大的价值总和?总和是多少?
解题思路
结题思路是把该问题分解为一个一个的小问题,一步步的通过小问题的最优解,最终得到大问题的最优解。在这个问题中有两个维度的变量,一是背包的容量M,二是物品的种类数N,假如M=1,只有一种物品 a,最优解是什么,有a,b两种物品是最优解是什么?以此类推。
状态转移方程怎么列?
1.首先,当物品种类N一定时,背包容量越大,那么最后的结果一定大于等于原来的值
2.其次,当背包容量M一定时,物品种类N越多,最后的记过一定大于等于原来的值。
3.那么,也就是说,我们想要求取的最大值也即是当M和N最大的时候的值。
假如当前有a和b两种,M=1时,考虑a是否能够放入,取得最大值,然后考虑a和b是否能放入,最终的情况可能只有a,或只有b或者ab都有。
对于b来说:
判断b能否单独放进背包:
—如果不能,那么备选为a,b时最大值,等于备选只有a时的最大值(因为b是放不进背包的)。
—如果能,即b能够放进去,还有两种可能(即将b放进背包,和不将b放进背包),对这两种可能性,要取最大值:
---------最终将b放进去(注:此时物品a是否被放进背包是未知的,原因是:剩余的背包容量可能不足以放进物品a,即要在剩余可选物品里找出最优解。
---------最终没有将b放进去(因为后面可能有比b更合适的物品放进去),此时最大值等于备选只有a时的最大值
引自原文
简单来说我们要思考的是第i个物品是否放进背包
c++代码如下:
if(j>=w[i])
dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
//dp[i][j]存储i个物品,容量为j时的最大值
else dp[i][j]=dp[i-1][j];
解题代码如下:
#include<iostream>
using namespace std;
int m,n;//n是物品种类数,m是背包容量
int w[100],v[100];
long long dp[100][100];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
else dp[i][j]=dp[i-1][j];
}
//上面的代码可以计算出最值,输出动态规划表
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
cout<<dp[n][m];
return 0;
}
回溯最优解
求出最大值后我们还要找出最后那几个物品被放进了背包,用回溯的方法来寻找最后解,思路如下
首先,判断dp[i][j]与dp[i-1][j-w[i]]+v[i]是否相等:
– 如果相等,则说明第i个物品没有放进背包,则继续回溯(i-1,j);、
– 否则就是进了背包,则回溯到(i-1,j-w[i])
定义函数如下:
void findMax(int i,int j){
if(i>=0){
if(dp[i][j]==dp[i-1][j]){
//如果满足这个条件,说明第i个没有放进背包
item[i]=0;
//那么就回溯到i-1,j
findMax(i-1,j);
}
else if(j-w[i]>=0 && dp[i][j]==dp[i-1][j-w[i]]+v[i])
{
item[i]=1;
//如果放进背包就回溯到下面
findMax(i-1,j-w[i]);
}
}
完整代码:
#include<iostream>
using namespace std;
int m,n;//n是物品种类数,m是背包容量
int w[100],v[100];
int item[100];//标记是否被选中
long long dp[100][100];
//回溯最优解
void findMax(int i,int j){
if(i>=0){
if(dp[i][j]==dp[i-1][j]){
//如果满足这个条件,说明第i个没有放进背包
item[i]=0;
//那么就回溯到i-1,j
findMax(i-1,j);
}
else if(j-w[i]>=0 && dp[i][j]==dp[i-1][j-w[i]]+v[i])
{
item[i]=1;
//如果放进背包就回溯到下面
findMax(i-1,j-w[i]);
}
}
//一直遍历直到i=0为止
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
else dp[i][j]=dp[i-1][j];
}
//上面的代码可以计算出最值,输出动态规划表
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++)
cout<<dp[i][j]<<" ";
cout<<endl;
}
//寻找最优解并输出
findMax(n,m);
for(int i=1;i<=n;i++) cout<<item[i]<<" ";
return 0;
}