視頻地址:
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 */
========== ========= ======== ======= ====== ===== ==== === == =
清平樂 · 六盤山 毛潤之
天高雲淡,望斷南飛雁。
不到長城非好漢,屈指行程二萬。
六盤山上高峰,紅旗漫卷西風。
今日長纓在手,何時縛住蒼龍?