動態規劃的入門可以先看這篇文章:動態規劃入門
揹包問題是動態規劃中最經典的乙個問題,要是比較難的乙個問題。
有乙個揹包,它的容量為c(capacity),現在有n種不同的物品,編號為0…n-1,其中每一件物品的重量為w(i),價值為v(i)。問可以向這個揹包中盛放哪些物品,使得在不超過揹包容量的基礎上,物品的總價值最大。
變數: c(容量) i(第i個物品) w(i) (第i件物品的重量) v(i)(第i件物品的價值)
要求:(1) w(0)+w(1)+…w(i-1)<=c
(2)v(0)+v(1)+…v(i-1)價值最大【 (2) v(0)+v(1)+…v(i-1)> v(0)+v(1)+…v(i-2)(錯誤)】
因為i代表是第i個物品,而第乙個物品的編號是0,所以注意最後下表要減一
思想理解:
注意:題目中說讓物品的總價值最大,並不是說盡最大可能把揹包裝滿同時不超過最大容量就行,而是盡可能挑選出那些高價效比(低容量**值)的優先裝。為了使揹包裡的價值最大,有些物品需要放進去,有些物品不需要放進去,因為有些物品可能會因為重量大價值又小使得價效比太低,比如揹包容量是5,有5個價值為10重量為1的物品,還有乙個價值為1容量為5的物品,如果選擇了將後者放到揹包裡,就放不進去其他物品,導致最終的價值極低。
所以,狀態轉移方程設成了這個樣子:
理解這個方**是費了老半天勁了!
這裡起初有很多疑問:
i個物品放進容量為c的揹包怎麼可能等於i-1個物品放進容量為c的揹包?
答:i代表是總共i個物品。當最後的第i個物品放不進去的時候,i個物品的價值就等於i-1個物品的價值。
如果是裝不下了那第二個等於後面公式就是能裝下的,能裝下為什麼是那樣?
對,第二個不是能裝下,而是必須裝這第i個物品,裝了之後再看剩下的空間裝剩下的i-1個物品。
後期這兩個等式進行比大小有什麼意義?很明顯只要容量夠後面一定大於前面啊?
有意義,兩者比較的是,不裝第i個物品的價值和必須裝第i個物品然後加上從剩下的空間裡得出的最大價值。不裝第i個物品的總數量i-1和裝第i個物品之後的總數量i-1不是同乙個東西。
有個大佬這麼跟我解釋:
這麼分類的思想是利用遞迴,自頂向下的角度來看這個問題,也就是說,第n次放進去的物品可以影響第n-1次,但是第n-1次是不能影響第n次。
但是我還是有點想不通,於是換了乙個思路:
接下來注意聽我說:
不要看截圖裡面的解釋,僅僅作為參考。
我們重新換一種新的理解:
f(n, c)表示從n件物品中,組合出x件(物品數量x可以大於n也可以小於n)價值最大且容量不超過c的物品放到揹包中。、
接下來尋找這些變數之間的乙個等價關係。
這個等價關係應該怎樣理解呢?注意聽:
等式左邊 f(i,c)表示從i件物品中挑出容量不小於c的組合的最大價值,它可能等於從i-1件物品中挑出容量不小於c的組合的最大價值,也就是說根本不用從i件物品裡面選最大價值,沒有你加入的最新的i件,同樣可以挑出最大價值組合。
它也有可能等於,首先在最大價值組合裡面,一定要把第i次挑出來的產品放進去。那這樣,這個最大價值組合的其他元素只能在i-1件物品裡面挑選容量之和不大於c-w(i)的物品,因為第i次挑選物品的確定,導致影響了在i-1件物品的挑選,所以出現f(i-1,c-w(i)),在i-1件物品中挑選出容量不大於c-w(i)的物品。
右邊的兩種情況,非常非常巧妙的利用了挑選物品一定算第i次挑選的物品和一定不算第i次挑選的物品這對互斥關係。最後從這兩種組合中選出價值最大的那乙個作為從i件物品中挑出的組大價值。
毫無疑問,遞迴是一定會有重複的,因為計算包含第i次的最大價值和不包含第i次的最大價值的過程一定會重複計算一些次的最大價值,所以出現記憶化遞迴。#include
#include
#include
using
namespace std;
/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積
/// 空間複雜度: o(n * c)
class
knapsack01
public
://從這裡開始看
//w代表重量集合,v是對應的價值集合,c是容量
intknapsack01
(const vector<
int>
&w,const vector<
int>
&v,int c)
};
遞迴到記憶化遞迴有三步:
1.將遞迴函式轉換為輔助函式(這道題已經是就不用轉換了),新建全域性容器,並在主函式裡面初始化大小(該題特殊之處在於初始化為二維陣列)。
2.在輔助函式中新增if條件語句,考慮當陣列容器為-1時的情況。
3.將遞迴等價關係取得的值記憶到陣列容器中去,最後返回容器。
這個二維陣列空間應該開多大,行數就是物品的數量,列數就是容量+1的大小。
表裡面的列代表第0-0個物品放置,第二列代表0-1兩個物品的放置,第三列代表0-2這三個物品的放置。
/// 揹包問題
/// 記憶化搜尋
/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積
/// 空間複雜度: o(n * c)
class
knapsack01
public
:int
knapsack01
(const vector<
int>
&w,const vector<
int>
&v,int c)
};記憶化遞迴到動態規劃總結:
1.又合併只有乙個函式,對陣列容器原地進行建立並初始化。
2.把遞迴的結束條件轉換為陣列容器的值。
3.利用遍歷和陣列容器中已存在的值,找到陣列容器中的等價關係(注意等價關係的容器陣列下標不再含有n),最後返回所需要的容器值。
注意:1.一定要搞清楚陣列容器中儲存的是什麼!
2.陣列容器的等價關係,一定要搞清楚誰利用誰,才能確定是從前往後還是從後往前遍歷!
這個就是自底向上的進行儲存了
#include
#include
#include
using
namespace std;
/// 揹包問題
/// 動態規劃
/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積
/// 空間複雜度: o(n * c)
class
knapsack01
return memo[n -1]
[c];}}
;
#include
#include
#include
using
namespace std;
/// 揹包問題
/// 動態規劃
/// 時間複雜度: o(n * c) 其中n為物品個數; c為揹包容積
/// 空間複雜度: o(n * c)
class
knapsack01
return memo[
(n -1)
%2][c];}
};
另一種階乘問題
大家都知道階乘這個概念,舉個簡單的例子 5!1 2 3 4 5.現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘例如 5 1 3 5.現在明白現在這種階乘的意思了吧!現在你的任務是求出1 2 n 的正確值 n 20 輸入 第一行輸入乙個a a 20 代表共有a組測試資料 ...
另一種階乘問題
描述 大家都知道階乘這個概念,舉個簡單的例子 5!1 2 3 4 5.現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘例如 5 1 3 5.現在明白現在這種階乘的意思了吧!現在你的任務是求出1 2 n 的正確值 n 20 輸入 第一行輸入乙個a a 20 代表共有a組測試...
另一種階乘問題
時間限制 3000 ms 記憶體限制 65535 kb 難度 1 描述 大家都知道階乘這個概念,舉個簡單的例子 5!1 2 3 4 5.現在我們引入一種新的階乘概念,將原來的每個數相乘變為i不大於n的所有奇數相乘例如 5 1 3 5.現在明白現在這種階乘的意思了吧!現在你的任務是求出1 2 n 的正...