0/1背包问题:
有N件物品和一个容量为V的背包,第i件物品消耗的容量为Ci,价值为Wi,求解放入哪些物品使背包中总价值最大。
仔细想想,这里每个物品只有一个,对于每个物品而言,只有两种选择,装入或不装入,装入记为1,不装入为0,我们不能将物品进行分割,比如只拿半个是不允许的。这就是这个问题被称为0/1背包问题的原因。
在选择的过程中,我们每做出一种选择,都会将一种情况分裂成两种,最终我们会得到一张决策图:
- 定义函数KS(i,j)
代表当前背包剩余容量为j时,前i个物品最佳组合所对应的价值
对于第i个物品,有两种可能:
- 背包剩余的容量不足以装下第i个物品,此时KS(i,j)等于KS(i-1,j)
- 背包剩余容量可以装下第i个物品,此时需要进行判断,因为装下该物品不一定会使最终结果是最优组合,此时我们在两中情况中选择最优情况如果不装该物品,则价值为KS(i-1,j),装下该物品的话,价值为KS(i-1,j-wight[i])+value[i]
- 状态转移方程
f[i][v]=max{ f[i-1][v],f[i-1][v-w[i]]+val[i] },考录问题“将前i个物品放入背包中的最大价值”,可能存在两种情况,即存在第i个物品和不存在第i个物品,我们只需在其中取最优解即可。
对于这个问题的子问题,这里有必要详细说明一下。原问题是,将n件物品放入容量为c的背包,子问题则是,将前i件物品放入容量为j的背包,所得到的最优价值为KS(i,j),如果只考虑第i件物品放还是不放,那么就可以转化为一个只涉及到前i-1个物品的问题。如果不放第i个物品,那么问题就转化为“前i-1件物品放入容量为j的背包中的最优价值组合”,对应的值为KS(i-1,j)。如果放第i个物品,那么问题就转化成了“前i-1件物品放入容量为j-wi的背包中的最优价值组合”,此时对应的值为KS(i-1,j-wi)+vi。
这里为了方便处理,将数组ws和vs都增加了一个补位数0,防止数组越界
int value[] = { 0,2,4,3,7 };
int wight[] = { 0,2,3,5,5 };
int result[5][11];
int f[100];
int ks(int i, int c)
{
if (i == 0 || c == 0)
return 0;
else if (c < wight[i])
return ks(i - 1, c); //第i个装不下
else
{
int temp1 = ks(i - 1, c - wight[i]) + value[i]; //装下第i个
int temp2 = ks(i - 1, c);//不装下第i个
int result = temp1 > temp2 ? temp1 : temp2;
return result;
}
}
另一种非递归的代码:
自上而下填表法:
int ks(int i, int c)
{
for(int k=1;k<=i;++k)
for (int j = 1; j <= c; ++j)
{
if (wight[k] > j)//容量不够
result[k][j] = result[k - 1][j];
else//容量足够
{
if (result[k-1][j] > result[k - 1][j - wight[k]] + value[i])
result[k][j] = result[k - 1][j];//不装该珠宝,最优价值最大
else
result[k][j] = result[k - 1][j - wight[k]] + value[k];
}
}
}
空间优化:
int ks(int m, int n)
{
for (int i = 1; i <= m; ++i)
for (int j = n; j >=wight[i]; --j)
f[j] = max(f[j], f[j - wight[i]] + value[i]);
return f[n];
}
完全背包问题
有N种物品和一个容量为T的背包,每种物品都就可以选择任意多个,第i种物品的价值为P[i],体积为V[i],求解:选哪些物品放入背包,可卡因使得这些物品的价值最大,并且体积总和不超过背包容量。
跟01背包一样,完全背包也是一个很经典的动态规划问题,不同的地方在于01背包问题中,每件物品最多选择一件,而在完全背包问题中,只要背包装得下,每件物品可以选择任意多件。从每件物品的角度来说,与之相关的策略已经不再是选或者不选了,而是有取0件、取1件、取2件…直到取T/Vi(向下取整)件。
- 状态转移方程
ks(i,t) = max{ks(i-1, t - V[i] * k) + P[i] * k}; (0 <= k * V[i] <= t)
同时,ks(0,t)=0;ks(i,0)=0;
其实只是在原来0/1背包问题的基础上加了一个循环
#include<iostream>
using namespace std;
int value[] = { 0,2,4,3,7 };
int wight[] = { 0,2,3,5,5 };
int nums[] = { 0,1,1,1,1 };
int ks(int m, int n)
{
int result = 0;
if (m == 0 || n == 0)
return 0;
if (wight[m] > n)
result=ks(m - 1, n);
else
{
for (int i = 0; i <= nums[m]; ++i)
{
if (ks(m - 1, n - i * wight[m]) + value[m] * i > result)
result = ks(m - 1, n - i * wight[m]) + value[m] * i;
}
}
return result;
}
填表法:
#include<iostream>
using namespace std;
int value[] = { 0,2,4,3,7 };
int wight[] = { 0,2,3,5,5 };
int nums[] = { 0,1,1,1,1 };
int result[5][11];
int ks(int m, int n)
{
for (int i = 1; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (j < wight[i])
result[i][j] = result[i - 1][j];
else {
/*if (result[i - 1][j] > result[i - 1][j - wight[i]] + value[i])
result[i][j] = result[i - 1][j];
else
result[i][j] = result[i - 1][j - wight[i]] + value[i];*/
for (int k = 0; k <= nums[i]; ++k)
if (result[i][j] < result[i - 1][j - k * wight[i]] + k * value[i])
result[i][j] = result[i - 1][j - k * wight[i]] + k * value[i];
else
result[i][j] = result[i - 1][j];
}
}
}
return result[m][n];
}
int main()
{
cout << ks(4, 10);
while (1);
}
优化空间:
#include<iostream>
using namespace std;
int value[] = { 0,2,4,3,7 };
int wight[] = { 0,2,3,5,5 };
int nums[] = { 0,1,1,1,1 };
int result[5][11];
int f[100];
int max(int a, int b)
{
return a > b ? a : b;
}
int ks(int m, int n)
{
for (int i = 1; i <= m; ++i)
{
for (int j = n; j >= wight[i]; --j)
for (int k = 0; k <= nums[i]&&j>=k*wight[i]; ++k)
f[j] = max(f[j], f[j - k * wight[i]] + k * value[i]);
}
return f[n];
}
int main()
{
cout << ks(4, 10);
while (1);
}
完全背包问题可以自底向上递推:
#include<iostream>
using namespace std;
int dp[100010];
int value[10010];
int time[10010];
int t, n;
int max(int a, int b)
{
return a > b ? a : b;
}
int ks(int t)
{
for (int i = 1; i <= t; ++i)
for (int j = 0; j < n; ++j)
if (i >= time[j])
dp[i] = max(dp[i], dp[i - time[j]]+value[j]);
return dp[t];
}
int main()
{
cin >> t >> n;
for (int i = 0; i < n; ++i)
cin >> time[i] >> value[i];
//for (int i = 1; i <= t; ++i)
//dp[i] = -1;
/*for (int i = 0; i < n; ++i)
for (int j = t; j >= time[i]; --j)
for (int k = 0; k*time[i] <= j; ++k)
dp[j] = max(dp[j], dp[j - k * time[i]] + k * value[i]);
cout << dp[t];*/
cout << ks(t);
//while (1);
return 0;
}
例题
贪吃的大嘴
有一只特别贪吃的大嘴,她很喜欢吃一种小蛋糕,而每一个小蛋糕有一个美味度,而大嘴是很傲娇的,一定要吃美味度和刚好为m的小蛋糕,而且大嘴还特别懒,她希望通过吃数量最少的小蛋糕达到这个目的.所以她希望你能设计一个程序帮她决定要吃哪些小蛋糕.
数据规模和约定
m ≤ 20000,小蛋糕总数量≤50.
输入
先输入一行包含2个整数m、n,表示大嘴需要吃美味度和为m的小蛋糕,而小蛋糕一共有n种,下面输入n行,每行2个整数,第一个表示该种小蛋糕的美味度,第二个表示蛋糕店中该种小蛋糕的总数
输出
输出一行包含一个整数表示大嘴最少需要吃的小蛋糕数量,若大嘴无法通过吃小蛋糕达到m的美味度和,则输出" > < “.
#include<iostream>
using namespace std;
int wight[55];
int nums[55];
int dp[20055];
const int INF = 0x3fffff;
int min(int a, int b)
{
return a < b ? a : b;
}
int main()
{
int m, n;
cin >> m >> n;
for (int i = 1; i <= n; ++i)
cin >> wight[i] >> nums[i];
dp[0] = 0;
for (int i = 1; i <= m; ++i)
dp[i] = INF;
for (int i = 1; i <= n; ++i)
{
for (int j = m;j>=wight[i]; --j)
{
for (int k = 0; k <= nums[i]; ++k)
if (dp[j - k * wight[i]] != INF)
dp[j] = min(dp[j], dp[j - k * wight[i]] + k);
}
}
if (dp[m]!=INF)
cout << dp[m];
else
cout << "><";
//while (1);
return 0;
}
解题思路:
把这个问题看成是背包问题,美味度当做背包的容量,数量当做价值。
注意初始化数组,因为背包最后必须恰好装满,所以如果存在答案,最后的dp[m]必定能够分解成dp[0]+dp[m-j]+…,可以发现,每次求解 KS(i,j)只与KS(i-1,m) {m:1…j} 有关。也就是说,如果我们知道了K(i-1,1…j)就肯定能求出KS(i,j)
礼物盒
#include<iostream>
using namespace std;
int dp[101];
const int INF = -1;
int wight[36] = { 0,11,8,11,16,1,2,6,10,17,10,6,5,2,19,4,7,5,5,15,3,15,11,9,17,9,4,10,12,17,19,20,11,10,20,3 };
int max(int a, int b)
{
return a > b ? a : b;
}
int main()
{
dp[0] = 0;
for (int i = 1; i <= 100; ++i)
dp[i] = INF;
for(int i=1;i<36;++i)
for (int j = 100;j>=wight[i]; --j)
if (dp[j - wight[i]] != INF) {
dp[j] = max(dp[j], dp[j - wight[i]] + 1);
printf("dp[%d]:%d\n", j, dp[j]);
}
cout << dp[100];
//while (1);
return 0;
}
小A点菜
求背包问题的方法数
定义f[i][j]为用前i道菜用光j元钱的办法总数,其状态转移方程如下:
(1)if(j==第i道菜的价格)f[i][j]=f[i-1][j]+1;
(2)if(j>第i道菜的价格) f[i][j]=f[i-1][j]+f[i-1][j-第i道菜的价格];
(3)if(j<第i道菜的价格) f[i][j]=f[i-1][j];
#include<iostream>
using namespace std;
int n, m;
int price[1010];
int f[110][10010];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> price[i];
for(int i=1;i<=n;++i)
for (int j = 1; j <= m; ++j)
{
if (j < price[i])
f[i][j] = f[i - 1][j];
else if (j == price[i])
f[i][j] = f[i - 1][j] + 1;
else
f[i][j] = f[i - 1][j - price[i]] + f[i - 1][j];
}
cout << f[n][m];
//while (1);
return 0;
}