背包问题
Problem Description
由于高数巨养的喵星人太傲娇了,要天天吃新鲜猫粮而且还经常欺负高数巨,所以高数巨决定买几条哈士奇尝尝鲜。这天高数巨来到了二手狗市场买哈士奇,高数巨看完了所有的哈士奇,记下了每条哈士奇的价格,并根据对它们的好感程度给它们每只都赋予了一个萌值。高数现在手里有 X X X 元,她想通过购买若干条哈士奇来获得尽可能多的萌值。现在给定高数巨手里的钱 X X X 以及 N N N 条哈士奇的价格和萌值,求高数巨最多可获得多少萌值。
Input
多组输入。对于每组输入,第一行有两个整数 N , X ( 1 ≤ N ≤ 100 , 1 ≤ X ≤ 1000 ) N,X(1 \leq N \leq 100,1 \leq X\leq1000) N,X(1≤N≤100,1≤X≤1000),分别表示哈士奇的数量和高数巨的钱数。接下来的 N N N行每行有两个整数 P i P_i Pi, M i ( 1 ≤ P i , M i ≤ 100 ) M_i (1 \leq Pi,Mi \leq 100) Mi(1≤Pi,Mi≤100),分别表示第 i i i 条哈士奇的价格和萌值。
Output
对于每组数据,输出一个整数,表示高数巨最多可以获得的萌值,每组输出占一行。
Sample Input
2 100
50 20
60 40
3 100
20 55
20 35
90 95
1 10
20 50
Sample Output
40
95
0
实例代码
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n, v;
int w[1001], p[1001];
while (~scanf("%d%d", &n, &v)) {
for (int i = 0; i < n; i++) {
// w 重量 | p 价值
scanf("%d%d", &w[i], &p[i]);
}
// 重新初始化最优值结果数组
int res[1001] = { 0 };
for (int i = 0; i < n; i++) { // 存储的物品个数
for (int k = v; k >= w[i]; k--) { // 从所需要的重量到当前重量
res[k] = max(res[k], res[k - w[i]] + p[i]);
}
}
printf("%d\n", res[v]);
}
}
最少硬币问题
Problem Description
设有
n
n
n 种不同面值的硬币,各硬币的面值存于数组
T
[
1
:
n
]
T[1:n]
T[1:n] 中。现要用这些面值的硬币来找钱。可以使用的各种面值的硬币个数存于数组
C
o
i
n
s
[
1
:
n
]
Coins[1:n]
Coins[1:n] 中。
对任意钱数
0
≤
m
≤
20001
0 \leq m \leq 20001
0≤m≤20001,设计一个用最少硬币找钱
m
m
m 的方法。
对于给定的
1
≤
n
≤
10
1 \leq n \leq 10
1≤n≤10,硬币面值数组
T
T
T 和可以使用的各种面值的硬币个数数组
C
o
i
n
s
Coins
Coins ,以及钱数
m
m
m ,
0
≤
m
≤
20001
0 \leq m \leq 20001
0≤m≤20001 ,计算找钱
m
m
m 的最少硬币数。
Input
输入数据第一行中只有1个整数给出 n n n 的值,第2行起每行2个数,分别是 T [ j ] T[j] T[j] 和 C o i n s [ j ] Coins[j] Coins[j] 。最后1行是要找的钱数 m m m。
Output
输出数据只有一个整数,表示计算出的最少硬币数。问题无解时输出 -1。
Sample Input
3
1 3
2 3
5 3
18
Sample Output
5
实例代码
#include <iostream>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int main()
{
int m, n, T[20001], Coin[20001];
int res[20001];
memset(res, INF, sizeof(res));
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> T[i] >> Coin[i];
}
cin >> m;
// res[目标面额] = 最小需求个数
res[0] = 0;
// 遍历面额值
for (int i = 0; i < n; i++)
// 遍历每种面额内有几张
for (int j = 1; j <= Coin[i]; j++)
// 遍历目标金额,选择当前这枚硬币是要还是不要
for (int k = m; k >= T[i]; k--)
// 将所有硬币依次拆分成单个,
// res[目标面额] = min(res[目标面额-当前面额]+1,res[目标面额]),
// 前一个是将该面额放入该组合中,后一个是不采用当前面额,选取数值最小的。
res[k] = min(res[k - T[i]] + 1, res[k]);
cout << (res[m] < INF ? res[m] : -1) << endl;
}
与“背包问题”的对比与分析
-
“背包”中给出了“损失”与“收益”,但其都是对于单个物品,求最大收益;“硬币”中给出的是“收益”、“个数”与“最终收益”,求最小组合。
-
依旧采用一维数组
res[目标面额]=最小需求个数
来存储最终结果。 -
将所有硬币依次拆分成单个,
res[目标面额] = min(res[目标面额-当前面额]+1,res[目标面额])
,前一个是将该面额放入该组合中,后一个是不采用当前面额,选取数值最小的。
空间复杂度优化 (以背包问题为例)
有很多人采用二维数组 res[i][j]
静态地更新来解决背包问题,使用二维数组更加直观,但是空间复杂度较高。
如果采用一维数组动态更新看起来比较难理解,但使用范围比较广。(比如,最少硬币问题中,三重循环,也可以采用一维数组来解决,但如果使用二维的话,对应地应该上升到三维)。
难点:
“k:v->w[i],依次递减”,这可能比较难以理解,在我们默认从小到大,而这里,必须从大到小,否则就是错误。
难点解答:
产生上述问题,本质就是对res[]
数组在某一时间其存储的值的不理解。
对于每一次更新开始,res[]
存储着的是“放入这个物品之前的最优解”,而我们比较的是 res[k]
与 res[k - w[i]] + p[i]
(背包问题),其中 res[k-w[i]]
是之前的最优解。
因为一定存在 k > k-w[i],如果从小到大,则一定会先与 res[k] 更新 res[k-w[i]],这就会导致比较错误。此时 res[k-w[i]] 已经变为“将该商品放入之后的最优价值”,在这价值基础之上,在加 p[i] ,其价值大概率会比 res[k] 高,而更新,这样,越来越大,完全背离了我们实际的意思。
因此,必须从后往前,因为前面存储着的是过去的值(不放第
i
i
i 件物品时的值)进行比较。