淺談貪心與動歸
淺談貪心與動歸
初學時 想必都會對兩者的認識有一些混淆
概念性質的就不贅述了
來談談我在刷題過程中對兩者的見解(誠心接受各位的指正)
從搜尋到貪心——求解演算法的優化 這篇文章非常值得一看
P1478 陶陶摘蘋果(升級版) 對應的oj題目
對比
貪心像是動歸的一個特例
動歸的核心在於:狀態轉移,找出那個轉移方程
貪心的核心在於:區域性選取最優解,並且選取的貪心策略不會影響到其他的狀態
用01揹包舉個例子
在n件物品取出若干件放在空間為c的揹包裡,每件物品的體積為w1 w2...wn,與之相對應的價值為v1 v2...vn,最終使揹包所裝物品的總價值最高
程式碼如下,基本上大家都會寫
int dp[i][j]; //dp[i][j] 表示取到第i個物品,揹包容量為j int pack01(int v[],int w[],int n,int c){ //value weight number capacity for(int i=1;i<=n;++i) for(int j=1;j<=c;++j){ if(w[i]>j) dp[i][j]=dp[i-1][j]; else dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); } } 空間優化 一維 for(int i=1;i<=n;i++) for(int j=c;j>=1;j--){ //注意從後往前 if(w[i]<=j){ //二維變一維 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } 更簡潔 for(int i=1;i<=n;i++) for(int j=c;j>=w[i];j--){ dp[j]=max(dp[j],dp[j-w[i]]+v[i]); }
但如果將v1 v2...vn這些每個物品對應的價值都變成1(v1=v2=...=vn=1)
這樣一來,每個物品的價值都相同。無論你有多重,你的價值和別人都一樣
很顯然,我們只要把物品的重量進行排序,先拿輕的再拿重的,結果必然最優
動歸加上一個特例 就這樣成為了貪心可解決的題目
並且時間複雜度O(n)直接下降到排序的nlogn
再來舉個更接地氣的栗子
找紙幣
基本上每個國家設計的貨幣都是符合貪心原則的
我國的紙幣面額分別為:100元、50元、20元、10元、5元、2元、1元
當需要找錢給別人時,先找大面值的紙幣再找小面值的,最後一定是紙幣數量最少的
但如果面額是1元、5元、11元的紙幣
當你找別人15元時,按照貪心規則,找的是一張11元和4張1元的,一共5張
這就不符合貪心策略了,這時怎麼來找到最少的?這就需要用到動歸了
這其實是完全揹包(每件物品可以取多次)的模板
必須把揹包裝滿,即正好找出零錢,不多也不少(會影響初始化,文末見揹包九講)
每個物品價值 v[i]=1,並且不是總價值最大,而是紙幣數量最小 max->min
int m[4]={0,1,5,11}; int dp[5][20]; //dp[i][j] i表示紙幣種類,j表示找回的零錢 //轉移方程 dp[i][j]=min(dp[i-1][j],dp[i][j-m[i]]+1); #define inf 10000 //若揹包則是-∞ //初始化 for(int i=0;i<=3;++i) for(int j=0;j<=15;++j){ if(j==0) dp[i][j]=0; else dp[i][j]=inf; } for(int i=1;i<=3;++i){ for(int j=0;j<=15;++j){ if(j>=m[i]) dp[i][j]=min(dp[i-1][j],dp[i][j-m[i]]+1); else dp[i][j]=dp[i-1][j]; //請注意轉移方程中dp[i][j-m[i]]+1裡的[i] //而01揹包是[i-1],這是物品能否取多次的關鍵所在 } } cout<<dp[3][15];
空間優化
int m[4]={0,1,5,11};
int dp[20];
#define inf 10000
for(int j=0;j<=15;++j){
if(j==0) dp[j]=0;
else dp[j]=inf;
}
for(int i=1;i<=3;++i){
for(int j=m[i];j<=15;++j){
dp[j]=min(dp[j],dp[j-m[i]]+1);
}
}
cout<<dp[15];
如果是第一種問法,要求恰好裝滿揹包,那麼在初始化時除了 F[0] 為 0 ,其它
F[1..V ] 均設為 −∞ ,這樣就可以保證最終得到的 F[V ] 是一種恰好裝滿揹包的最優解。
如果並沒有要求必須把揹包裝滿,而是隻希望價格儘量大,初始化時應該將 F[0..V ]
全部設為 0 。
這是為什麼呢?可以這樣理解:初始化的 F 陣列事實上就是在沒有任何物品可以放
入揹包時的合法狀態。如果要求揹包恰好裝滿,那麼此時只有容量為 0 的揹包可以在什
麼也不裝且價值為 0 的情況下被“恰好裝滿”,其它容量的揹包均沒有合法的解,屬於
未定義的狀態,應該被賦值為 -∞ 了。如果揹包並非必須被裝滿,那麼任何容量的揹包
都有一個合法解“什麼都不裝”,這個解的價值為 0 ,所以初始時狀態的值也就全部為 0
了。
取自《揹包問題九講》1.4 初始化的細節問題