01背包
视频地址:
https://www.bilibili.com/video/BV1U5411s7d7?
一,0-1 背包题目
给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。其中,每件物品都只能选择一次。
二,错误的思考
之前曾经想到,可以求出每件物品的单价(即 价格/重量),在根据单价进行排序和选择。后来写出来答案不对,才发现这样做有个问题,即这里的物品并不是真的称斤买的。
例如,你背包空间剩下 4,此时还剩下两件物品没有决策,一个 w:1,v: 5,一个 w: 4,v: 6。这时,两件物品无法同时选择,答案很明显应该选第二个,但是在这种方法下,会选择第一个,因为第一个单价更高。
这种方法会出错的原因在于:到了最后,尽管你单价更高,但你的空间利用的不够。
错误的代码:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<algorithm> using namespace std; #define N 100 struct Node { int w, v; double av; }kp[N]; bool vis[N]; // 标记这个物品是否偷过 bool cmp(Node a, Node b) { return a.av - b.av < 1e-6; } int W, n; // W 背包的最大空间 ,n 代表物品个数 int V; // 最大价值 void knapsack_01() { for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { if (kp[j].w < W&&vis[j] == 0) { vis[j] = 1; W -= kp[j].w; V += kp[j].v; } } } } int main(void) { scanf("%d%d", &n, &W); for (int i = 1; i <= n; i++) { scanf("%d%d", &kp[i].w, &kp[i].v); kp[i].av = (double)kp[i].v / (double(kp[i].w)); } sort(kp + 1, kp + n + 1, cmp); knapsack_01(); printf("%d\n", V); system("pause"); return 0; } /*测试数据: 5 20 2 3 3 4 4 5 5 8 9 10 */
三,算法分析
这种 01背包是比较一般性的动态规划问题,即可以由前面的状态堆出后面的状态。所以可以从函数关系和函数出口概括这种问题。( 。ớ ₃ờ)ھ
设 B(k,w) :
如果背包共计能装 w 重的商品,那么在只能选择前 k 个商品的前提下,背包能装的物品的最大的价值。
则有
函数关系:
在对 B(k, w) 进行分解时,可以将其分为三种情况:
B(k, w) = max ①②③
① 为 B(k-1, w) 代表 第 k 件放不下,所以偷不了
② 为 B(k-1, w) 代表 第 k 件放得下,但选择不偷
③ 为 B(k-1, w-wk) + vk 代表 第 k 件放得下,且很选择偷
函数出口:
B(0, w) = 0 一个都不能偷,偷个寂寞
B(k, 0) = 0 一个都放不下,望洋兴叹
四,代码
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #define MAX(x,y) (x>y?x:y) #define N 100 int b[N][N]; int w[N], v[N]; // 商品重量,商品价值 int n, m; // 物品个数,背包体积 int dp(int k, int m) { if (k == 0) return 0; if (w == 0) return 0; if (w[k] > m) // 放不下 return dp(k - 1, m); return MAX(dp(k - 1, m - w[k]) + v[k], dp(k - 1, m)); } void knapack_01() { for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (w[i] > j) // 放不下 b[i][j] = b[i - 1][j]; else b[i][j] = MAX(b[i - 1][j - w[i]] + v[i], b[i - 1][j]); } } } void show() { printf("\nw == > "); for (int i = 0; i <= m; i++) printf("%3d", i); puts(""); for (int i = 0; i <= n; i++) { printf("第%d件: ", i); for (int j = 0; j <= m; j++) { printf("%3d", b[i][j]); }puts(""); } printf("当背包可装重 %d 的物品时,最多可偷 %d 价值的物品\n", m, dp(n, m)); } int main(void) { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) // 商品下标从1开始 scanf("%d%d", &w[i], &v[i]); knapack_01(); // 01 背包 show(); system("pause"); return 0; } /*测试数据: 5 20 2 3 3 4 4 5 5 8 9 10 */
========== ========= ======== ======= ====== ===== ==== === == =
清平乐 · 六盘山 毛润之
天高云淡,望断南飞雁。
不到长城非好汉,屈指行程二万。
六盘山上高峰,红旗漫卷西风。
今日长缨在手,何时缚住苍龙?