背包问题,基本上都是以一个固定的容积S,和n件有重量cost和价值value的物品,求价值最大的一种放法。
有人说背包问题实际上运用了贪心的思想,但是我觉得背包的思路与贪心大不相同,贪心的算法更多的是针对每一步变化的策略选取最优解,而不是想背包问题一样对于每一个可放置重量都进行规划,背包是一个动态的过程。
首先我们来说说最简单的01背包问题,每个物品只能放一次,我们采取的做法是将所有可以放下的物品都进行判断,是否最优,如果最优则更新最优解
很显然,对该种规划可以在输入的时候进行,以减小对空间的消耗。#include<bits/stdc++.h> using namespace std; const int N = 1e3 + 7; typedef long long ll; int cost[N],value[N],dp[N][N]; // 存储花费,价值,动态规划数组 int main () { int n,S; // n表示物品个数,S表示背包容量 cin >> n >> S; memset(dp,0,sizeof dp); // dp数组清零 for(int i = 1;i <= n;++i) cin >> cost[i] >> value[i]; for(int i = 1;i <= n;++i) for(int j = 0;j <= S;++j){ dp[i][j] = i == 1 ? 0 : dp[i - 1][j]; // 继承上一件物品规划时候的状态 if(j >= cost[i]) dp[i][j] = max(dp[i - 1][j - cost[i]] + value[i],dp[i][j]); // 进行最优的动态规划 } cout << dp[n][S] << endl; return 0; }
当我们将这种变化用表格的形式打出来的时候,我们惊讶的发现,对于物品个数的规划是没有必要的,我们只需要针对每一种花销进行规划,这样我们只需要用一维数组就可以完成上述过程,大大的减小了对空间的消耗。#include<bits/stdc++.h> using namespace std; const int N = 1e3 + 7; typedef long long ll; int dp[N][N]; int main () { int n,S; cin >> n >> S; memset(dp,0,sizeof dp); int cost,value; for(int i = 1;i <= n;++i){ cin >> cost >> value; for(int j = 0;j <= S;++j){ dp[i][j] = i == 1 ? 0 : dp[i - 1][j]; if(j >= cost) dp[i][j] = max(dp[i - 1][j - cost] + value,dp[i][j]); } } cout << dp[n][S] << endl; return 0; }
#include<bits/stdc++.h> using namespace std; const int N = 1e3 + 7; typedef long long ll; int dp[N]; int main () { int n,S; // cin >> n >> S; memset(dp,0,sizeof dp); int cost,value; for(int i = 1;i <= n;++i){ cin >> cost >> value; for(int j = S;j >= 0;--j){ if(j >= cost) dp[j] = max(dp[j - cost] + value,dp[j]); // 针对每个可放容量来选择放与不放 } } cout << dp[S] << endl; return 0; }
-
完全背包问题 ,完全背包问题就是在01背包问题的基础上将每个物品可放次数变成了无限,我们只需要将01背包的代码改成顺着遍历即可。因为顺着遍历,该种物品就在每个容量上被多次规划了。
#include<bits/stdc++.h> using namespace std; const int N = 1e3 + 7; typedef long long ll; int dp[N]; int main () { int n,S; // cin >> n >> S; memset(dp,0,sizeof dp); int cost,value; for(int i = 1;i <= n;++i){ cin >> cost >> value; for(int j = cost;j <= S;++j){ if(j >= cost) dp[j] = max(dp[j - cost] + value,dp[j]); } } cout << dp[S] << endl; return 0; }
-
多重背包问题.
题目限制了每种物品的购买次数.所以我们可以将多出的部分按照01背包的规划方式进行规划.
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
typedef long long ll;
int dp[N];
int main ()
{
int n,S; //
cin >> n >> S;
memset(dp,0,sizeof dp);
int cost,value,time;
for(int i = 1;i <= n;++i){
cin >> cost >> value >> time;
while(time--){
for(int j = S;j >= cost;--j)
if(j >= cost) dp[j] = max(dp[j - cost] + value,dp[j]);
}
}
cout << dp[S] << endl;
return 0;
}
由于wustoj只能判多组数据...所以ac代码就是加上while循环...
4.混合背包问题
其实后面的背包问题基本就是由01背包和完全背包的组合.
而混合背包其实就是01背包,完全背包,多重背包的大杂烩.还是上例题吧.
在多重背包的基础上多了一个如果个数上限为0即可购买个数无上限.
其实就是在多重背包的基础上多了一次条件判断,决定是顺着遍历还是逆着遍历.
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 7;
typedef long long ll;
int dp[N];
int main ()
{
int n,S;
cin >> n >> S;
memset(dp,0,sizeof dp);
int cost,value,time;
for(int i = 1;i <= n;++i){
cin >> cost >> value >> time;
if(time)
for(int o = 0;o < time;++o)
for(int j = S;j >= cost;--j)
dp[j] = max(dp[j - cost] + value,dp[j]);
else
for(int j = cost;j <= S;++j)
dp[j] = max(dp[j - cost] + value,dp[j]);
}
cout << dp[S] << endl;
return 0;
}
5.多种开销的背包问题(这里只说两种开销的背包问题,更多开销的背包问题方法一样)
其实这种问题与01背包一样,只不过从01背包的一个开销规划变成了两种,通过一个二维数组就可以实现.
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7;
typedef long long ll;
int dp[N][N];
int main ()
{
int n,S1,S2;
cin >> S1 >> S2 >> n;
memset(dp,0,sizeof dp);
int cost1,cost2,value;
for(int i = 1;i <= n;++i){
cin >> cost1 >> cost2 >> value;
for(int j = S1,k = S2;j >= cost1 && k >= cost2;--j,--k)
dp[j][k] = max(dp[j - cost1][k - cost2] + value,dp[j][k]);
}
cout << dp[S1][S2] << endl;
return 0;
}
上一个例题,wustoj 1873潜水员,这个题目也是一个二维背包问题,但是题目要求两种开销达到某一值后的最小值,这就比较有意思了,我们采取的策略是设置两个计数器来记忆已花费开销,当两种开销都达到标准之后按照标准值进行规划。跟01背包相比不需要小于背包的容量。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int dp[N][N];
int main ()
{
int n,S1,S2;
cin >> S1 >> S2 >> n;
memset(dp,inf,sizeof dp);
dp[0][0] = 0;
int cost1,cost2,value,date1 = 0,date2 = 0;
for(int i = 1;i <= n;++i){
cin >> cost1 >> cost2 >> value;
for(int j = S1;j >= 0;--j)
for(int k = S2;k >= 0;--k){
date1 = j + cost1;
date2 = k + cost2;
if(date1 > S1) date1 = S1;
if(date2 > S2) date2 = S2;
dp[date1][date2] = min(dp[j][k] + value,dp[date1][date2]);
}
}
cout << dp[S1][S2] << endl;
return 0;
}
6.分组背包
分组背包是在01背包的基础上将物品分组,同一组的物品只能同时存在一个。看起来好像不好下手,其实想通了也很简单。
我们将每一组物品都用同一层状态转移这样,我们可喜的发现,我们的问题就直接转化成了01背包问题。
还是上一个例题吧
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 7;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int dp[N];
int cost[N][N],value[N][N];
int main ()
{
int n,S,t;
cin >> S >> n >> t;
memset(dp,0,sizeof dp);
memset(cost,0,sizeof cost);
memset(value,0,sizeof value);
int num,c,v;
for(int i = 1;i <= n;++i){
cin >> c >> v >> num;
cost[num][++cost[num][0]] = c;
value[num][++value[num][0]] = v;
}
for(int i = 1;i <= t;++i)
for(int j = S;j >= 0;--j)
for(int k = 1;k <= cost[i][0];++k)
if(j >= cost[i][k]) dp[j] = max(dp[j - cost[i][k]] + value[i][k],dp[j]);
cout << dp[S] << endl;
return 0;
}