动态规划练习【一】 背包问题
詳細講解
背包問題大匯總
文章目錄
- 背包問題大匯總
 - 01背包
 - 問題:
 - 思路:
 - 空間優化復雜度
 - 代碼
 - 總結:
 
- 完全背包
 - 問題:
 - 思路:
 - 代碼:
 - 優化
 
- 多重背包
 - 問題:
 - 思路:
 - 代碼:
 - 單調隊列優化
 
- 混合三種背包
 - 問題:
 - 思路
 - 例題
 - 代碼:
 
- 二維費用的背包問題
 - 分組的背包問題
 - 有依賴的背包問題
 - 背包問題的方案總數
 
01背包
問題:
有N件物品和一個容量為V的背包,第i件物品的費用(體積)是w[i],價值是c[i],求解將哪些物品裝入背包可使這些物品的費用綜合不超過背包容量,且價值總和最大
思路:
f[i][v]表示前i件物品(部分或全部)恰放入一個容量為v的背包可以獲得的最大價值。
 轉移方程:
 (f的初始值為負無窮)
 f [ i ][ v ] = m a x ( f [i - 1 ] [ v ] , f [i - 1 ] [ v -w [ i ] ] + c [ i ] )
 這是最基礎的背包dp,我們在考慮dp問題的時候,這么想,f[i][v]的狀態是怎么來的?也就是第i件物品選擇方式?有兩種方式,第一種就是第i件商品不拿,那么f[i][v]的狀態就是前i-1件物品恰放入一個容量為v的背包(即f[i-1][v])直接轉移過來的,另外一種就是第i件商品選擇拿,那么f[i][v]的狀態就是前i-1件物品恰放入一個容量為v-w[i]的背包(即f[i-1][v-w[i]])再加入第i件商品(c[i]),其實就是末狀態(f[i][v])將第i件商品去除,可以得到初狀態(f[i-1][v-w[i]])
 本質就是由已知推未知
 這樣最后答案是max(f[N][0…V]),因為我們在定義狀態f時說的“恰”,也就是最佳情況并不一定是背包要裝滿,所以要取各種狀態的最大值。
 怎么樣這個“恰”去掉呢?我們可以將所有f[i][j]初始化為0,也就是最開始的f[i][j]是有意義的,只是價值為0,可以看做無物品的背包價值都是0,這樣最后的結果就是f[N][V]
 小結:初始化為0------>不超過容量V
 初始化為負無窮,dp[i][0]=0-------->恰好為容量V
空間優化復雜度
我們可以用一維的f[i]來代替上面的二維數組
 for i=1…N
 for v=V…0
 f [v ] = max ( f[ v ] , f[ v- w [ i ] ] + c [ i ] )
 我們在每次主循環時用v=V…0的逆序推f[v],這樣才能保證推f[v]時f[v-w[i]]保存的是狀態f[i-1][v-w[i]]的值。
 為什么倒著循環可以看看這篇文章
代碼
二維:
//f[i][v]表示前i件物品,總質量不超過v的最優價值 for(int i=1;i<=N;i++) {for(int v=V;v>0;v--){if(w[i]<=v)f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);//如果能裝下 else f[i][v]=f[i-1][v];} } printf("%d",f[N][V]);一維:
//f[v]表示質量不超過v公斤的最大價值 for(int i=1;i<=N;i++) {for(int v=V;v>=w[i];v--)//避免數組出現負下標{f[v]=max(f[v],f[v-w[i]]+c[i]);} } printf("%d",f[V]);總結:
01背包定義函數一般有dp[i]恰好花費i最佳和花費i最佳,前者是dp初始為最大值,后者初始為0
 如果dp第一維不是考慮前i個商品,而是直接考慮花費這類,此時花費這類的循環要倒著進行,這樣可以優化空間復雜度
完全背包
問題:
有N種物品和一個容量為V的背包,每種物品都有無限件可用。第i種物品的費用是w[i],價值是c[i].求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
思路:
和01背包的區別在于,每件物品不再只有取和不取兩種狀態,而是有取0件,取1件,取2件…很多種。
 我們用01背包的方式來做,f[i][v]表示前i種物品恰好放入一個質量為v的背包的最大權值
 f [ i ] [ v ] = m a x (f [ i - 1 ] [ v - k * w [i ] ] + k * c [ i ] | 0 < =k * w [ i ] <= v )
 使用一維方程:
 for i=1…N
 for v=0…V
 f [v ] = m a x (f [ v ] , f[ v - w [i ] ] +c [ i ] )、
 你會發現與01背包相比,僅僅是把v的枚舉順序倒了過來
 為啥呢?
 01背包中v是從V->0,這是因為為了保證第i次循環中的狀態f[i][v]是由上一個狀態f[i-1][v-w[i]]遞推而來,這樣可以確定每個物品只選一次,我們在考慮“加選第i件物品”這一策略時,依據的是一個絕無已經選入第i件物品的子結果f[i-1][v-w[i]]
 而完全背包中物品的數量是無限的,所以在考慮“加選第i件物品”時,需要的是一個已經選入第i種物品的子結果f[i][v-w[i]],也就是再已放入第i種物品的基礎上再進行轉移,所以要采用v=0->V的順序
 二維方程的話:
 f [ i ] [ v ] = max ( f [ i - 1 ] [ v ] , f[ i ] [ v - w [ i ] ]+ c [ i ] )
代碼:
f[i][v]表示前i件物品,總質量不超過v的最優價值
for(int i=1;i<=N;i++) {for(int v=0;v<=V;v++){if(v<w[i])f[i][v]=f[i-1][v];else {f[i][v]=max(f[i-1][v],f[i][v-w[i]]+c[i]);}}} printf("%d",f[N][V]);f[v]表示質量不超過v公斤的最大價值
for(int i=1;i<=N;i++) {for(int v=0;v<=V;v++){if(v>w[i])f[v]=max(f[v-w[i]]+c[i],f[v]); } } printf("%d",f[V]);優化
完全背包中,如果兩個物品i與j,w[i]<=w[j]&&c[i]>=c[j],物品j就可以去掉,也就是一個物品占空間又大,價值還低,那要他干啥,j完全可以被i代替。這是一個簡單的小優化
 我們還可以把完全背包問題轉化成01背包問題來解
 因為背包容量是給定的,所以就可以算出每件物品最多可以裝多少,將無限數量轉變成有限數量,用01背包去做
 但是這樣的轉變也太low了,對程序優化不了多少,更高效的轉化是:把第i種物品拆成費用為w[i]*2k,價值拆成c[i]*wk的若干件物品,k滿足w[i]*2k<V。二進制拆分的原理我們可以用 1,2,4,8…2n 表示出1 到 2n+1?1的所有數.利用二進制的思想來優化問題,不管最優策略是選幾件第i種商品,總可以寫成若干個2k件物品的和。把每件物品拆成O(log(V/w[i])+1)件物品
多重背包
問題:
有N種物品和一個容量為V的背包,第i種物品最多有n[i]件可用,每件費用是w[i],價值是c[i].求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
思路:
和完全背包很相似,區別在于每個物品的數量是明確的是固定的,那直接就可以把完全背包的式子拿過來
 f [ i ] [ v ] = m a x (f [ i - 1 ] [ v - k * w [i ] ] + k * c [ i ] | 0 < =k * <= n[i] )
 一維的就是 f [ v] = max ( f[ v ] , f [ v - k * w [ i] ] + k* c [ i ] );
 區別是k的范圍是0~n[i]
 但是光能這么做怎么夠,我們看看怎么轉化成最基礎的01背包
 最簡單粗暴的就是你吧第i件物品換成n[i]件01背包中物品,就是把一種多件看作是多種一件,但是這樣復雜度不變O(V * sum(n[i])),意義不大,怎么才能降低負責度
 完全背包我們講了二進制的方式來優化,這里我們也考慮二進制的思想。把第i種物品換成若干件物品,每個物品有一個系數,這件物品的費用和價值等于原本物品的乘以系數,而這些系數分別是2的k次方(系數為0,2,4,),最后一個系數是n[i]-2k+1(剩下的)。例如13就可以拆分成系數為1,2,4,6(因為8達不到,所以用13減去前面的1,2,4,得到6)的四件物品
 這樣就將第i種物品分成O(logn[i])種物品,將原問題轉化為了復雜度為O(V*sum(logn[i])的01背包問題
代碼:
樸素算法:
for(int i=1;i<=n;i++) {for(int j=V;j>=0;j--){for(int k=0;k<=n[i];k++){if(j-k*w[i]<0)break;//如果當前情況容量不足,那后面容量肯定也不夠,直接跳出換成其他物品 f[j]=max(f[j],f[j-k*w[i]]+k*c[i]);}} } cout<<f[V];進行二進制優化,轉化成01背包
int main() {cin>>n>>V;for(int i=1;i<=n;i++)//把一種物品的數量按照二進制進行拆分,并乘以對應系數 {int x,y,s,t=1;cin>>x>>y>>s;//輸入費用,單價值,數量 while(s>=t){n1++;w[n1]=x*t;//乘以系數 c[n1]=y*t;s-=t;t*=2;}w[++n1]=x*s;//剩余費用 c[n1]=y*s;//剩余價值 }for(int i=1;i<=n1;i++){for(int j=V;j>=w[i];j--){f[j]=max(f[j],f[j-w[i]]+c[i]);//01背包解法 }}cout<<f[V]; } for(int i=1;i<=n;i++) {for(int j=1;j<=num[i];j<<=1)//二進制每一位枚舉.//注意要從小到大拆分{num[i]-=j;//減去拆分出來的new_c[++tot]=j*c[i];//合成一個大的物品的體積new_w[tot]=j*w[i];//合成一個大的物品的價值}if(num[i])//判斷是否會有余下的部分.//就好像我們某一件物品為13,顯然拆成二進制為1,2,4.//我們余出來的部分為6,所以需要再來一份.{new_c[++tot]=num[i]*c[i];new_w[tot]=num[i]*w[i];num[i]=0;} }單調隊列優化
多重背包最普通的狀態方程:
 f [ i ][ j ] = max ( f [ i ? 1 ] [ j ] , f [ i ? 1 ] [ j ? k ? c [ i ] ] + k? w [ i ] )
混合三種背包
問題:
如果把01背包,完全背包,多重背包混合起來。也就是說,有的物品只可以去一次(01背包),有的物品可以無限取(完全背包),有的物品可以取的次數有一個上限(多重背包),應該怎么求解呢?
思路
01背包和完全背包代碼中只有v的循環順序不同,所以可以針對不同的商品類型選用順序或者逆序的循環即可
 for i=1…N
 if 第i件物品是01背包
 for v = V…0
 f [ v ] = max ( f [ v] , f [ v - w [ i ] ] + c [ i ] )
 else if 第i件物品是完全背包
 for v=0…V
 f [ v ] = max ( f [ v ] , f [ v- w [ i ] ] + c [ i ] )
 再加上多重背包
 將多重背包分成多個01背包然后求解
例題
一個容量為m的背包,現在有n件物品,質量分別是W[i],價值分別是C[i],P[i]表示物品的數量,(0說明可以購買無數次,其他數字為購買的最多件數)
代碼:
for(int i=1;i<=n;i++) {if(完全背包) {for(int j=c[i];j<=V;j++)f[j]=max(f[j],f[j-c[i]]+w[i]);}else if(01背包) {for(int j=V;j>=c[i];j--)f[j]=max(f[j],f[j-c[i]]+w[i]); }else//否則就是多重背包了 {for(int j=V;j>=0;j--)for(int k=1;k<=num[i];k++)if(j-k*c[i]>=0)f[j]=max(f[j],f[j-k*c[i]]+k*w[i]);} } int main() {cin>>m>>n;for(int i=1;i<=n;i++)cin>>w[i]>>c[i]>>p[i];for(int i=1;i<=n;i++){if(p[i]==0)//完全背包{for(int j=w[i];j<=m;j++)f[j]=max(f[j],f[j-w[j]]+c[i]);}else {for(int j=1;j<=p[i];j++)//01背包和多重背包 {for(int k=m;k>=w[i];k--){f[k]=max(f[k],f[k-w[i]]+c[i]);}}} }cout<<f[m]; } #include<bits/stdc++.h> using namespace std; const int maxn=1e4+7; int w[maxn],v[maxn],tot[maxn]; int dp[200010]; int main() {int V,n;scanf("%d%d",&n,&V);for(int i=1; i<=n; i++)scanf("%d%d%d",&w[i],&v[i],&tot[i]);for(int i=1; i<=n; i++){if(tot[i]==0)///完全背包for(int j=w[i]; j<=V; j++)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);else///01與多重背包(二進制優化后的多重背包與01背包循環非常相似){int x=tot[i];for(int k=1; k<x; k<<=1)///此處套用二進制優化的模板{for(int j=V; j>=w[i]*k; j--)dp[j]=max(dp[j],dp[j-w[i]*k]+v[i]*k);x-=k;}if(x)for(int j=V; j>=w[i]*x; j--)///相當于還剩一個物品未考慮dp[j]=max(dp[j],dp[j-w[i]*x]+v[i]*x);}}cout<<dp[V];return 0; }二維費用的背包問題
分組的背包問題
有依賴的背包問題
背包問題的方案總數
總結
以上是生活随笔為你收集整理的动态规划练习【一】 背包问题的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 玉林房地产备案(玉林地产备案)
 - 下一篇: unix系统与linux属于同一类系统吗