日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

动态规划练习【一】 背包问题

發布時間:2023/12/3 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划练习【一】 背包问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

詳細講解

背包問題大匯總

文章目錄

    • 背包問題大匯總
    • 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; }

二維費用的背包問題

分組的背包問題

有依賴的背包問題

背包問題的方案總數

總結

以上是生活随笔為你收集整理的动态规划练习【一】 背包问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。