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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

变种 背包问题_【朝夕的ACM笔记】动态规划-背包问题

發布時間:2024/9/19 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 变种 背包问题_【朝夕的ACM笔记】动态规划-背包问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【朝夕的ACM筆記】目錄與索引

背包問題

一、0/1背包

1.1 問題描述

有 件物品和一個大小為 的背包,以及若干物品,每種物品只有一件,大小分別為 ,其價值分別為 。問題:將哪些物品裝入背包,可使得背包內的物品價值總和最大?
輸入:
第一行:
接下來 行,每行兩個整數, 和 。
輸出:最大價值 .

1.2 解題思路

由于每種物品都只有一件,所以只存在兩個選擇:裝這個物品或者不裝。(也就是0/1背包的含義)

我們定義一個數組

用于儲存——當背包大小為 ,且選擇處理完第 個物品后(前 個物品選擇裝或不裝)的最大價值總和,那么最終的答案就是 。

我們通過一個例子來推出

的遞推式。

有3個物品,大小價值如下表,和一個大小為3的背包。

先考慮裝不裝A。如下表。

由于A的大小只有1,所以大小1~3的背包都可以裝下它,于是更新所有值為A的價值2。

接下來考慮B。如下表。

由于B的大小為2,所以大小為1的背包裝不下它,值不變。

大小為2的背包可裝下它,且值為5,比原來的2大,于是更新值為5。

大小為3的背包可裝下它,且裝完后剩余大小為1。

如果裝B,那么價值=B的價值+未裝B的、大小為1的背包的最大價值=5+2=7。

如果不裝B,那么價值就是原來的值=2。在二者里取max,就能得到最大值,也就是7。

最后考慮C。如下表。

C的大小為1,背包1可裝,值更大,更新為3。

背包2可裝,如果裝,那么價值=C的價值+未裝C的、大小為1的背包的最大價值=3+2=5。

如果不裝,值就是原來的5。二者取

即可。

背包3可裝,如果裝,那么價值=C的價值+未裝C的、大小為2的背包的最大價值=5+3=8。

如果不裝,值就是原來的7。二者取

即可。

所以最后輸出的值就是8。

小結論:

我們可以發現遞推公式:

也就是處理第

件物品,背包大小為 時的最大價值,有兩種可能。

①裝入第

件物品

那么價值=該物品的價值+未裝入該物品的、大小足夠塞入該物品的背包的最大價值=

②不裝入第

件物品

那么價值就是處理

件物品時計算出來的值,不變。

二者取最大值即可。

1.2.1 代碼:

#include <stdio.h> int max(int a,int b) {if(a>b) return a;else return b; } int bag[1000][1000];//儲存解的數組 int w[1000],val[1000];//每件物品的大小與價值 int main() {int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&val[i]);//讀入數據for(int i=1;i<=n;i++) //從第一件物品一直處理到第i件物品for(int j=1;j<=m;j++) //從大小為1的背包處理到大小為m的背包if(j>=w[i])//塞得進去這件物品的話bag[i][j]=max(bag[i-1][j],bag[i-1][j-w[i]]+val[i]);//就判斷是塞還是不塞比較好else//塞不進去就是原來的值bag[i][j]=bag[i-1][j];printf("%dn",bag[n][m]);//全部處理完后,輸出最后的答案return 0; }

1.3 算法的空間優化

1.3.1 為什么要優化?

通過1.3的代碼可以很容易的看出,當物品有

件,背包大小為 時,我們就需要開一個 大小的數組了,要是處理的數據再稍微大一點,就會導致數組開的過大,然后內存溢出。

所以為了解決更大的數據,空間的優化是必要的。

1.3.2 怎么優化?

我們可以發現,當我們處理第

件物品時,要用到的只是處理第 件物品時的數據,再之前的就用不到了。

所以,我們可以每次處理物品時,就把數據進行一次覆蓋,這樣二維數組就能降維到一維了。

但需要注意!降維到一維數組后,我們處理第

件物品時,就不能從大小為1的背包一直順序處理到大小為 的背包了。為什么?因為這樣的話,處理 件物品時的數據會在本次處理中被覆蓋,而處理更大的背包時是會用到小背包的數據的。

這樣就會出現——已經塞了第

件物品了,處理時就當做沒塞,就會WA了。

所以我們這里進行倒序處理,因為處理小背包用不到大背包的數據,覆蓋了也就覆蓋了,無所謂。

1.3.3 優化代碼

#include <stdio.h> int max(int a,int b) {if(a>b) return a;else return b; } int bag[1000];//降維到一維的數組 int w[1000],val[1000];//每件物品的大小與價值 int main() {int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&val[i]);//讀入數據for(int i=1;i<=n;i++) //從第一件物品一直處理到第i件物品for(int j=m;j>0;j--) //倒序處理if(j>=w[i])//塞得進去這件物品的話bag[j]=max(bag[j],bag[j-w[i]]+val[i]);//就判斷,處理后覆蓋上一次處理的數據else//塞不進去就是原來的值bag[j]=bag[j];//這行代碼完全沒有意義,但是為了方便對比,留在這里printf("%dn",bag[m]);//全部處理完后,輸出最后的答案return 0; }

優化完后,空間復雜度就從

縮小到了 ,大大節省了內存,也就可以處理更大的數據了。

1.4 0/1背包的常見例題及變種

1.4.1 采藥問題 洛谷 P1048

有 株藥草,每株藥草有其對應的價值及采摘該藥草要花費的時間,在 時間內,求采摘的藥草的最大價值。

這其實就是換了個題干的0/1背包模板題,上面的代碼直接復制粘貼就可AC。

1.4.2 購物問題 洛谷 P1060

你有 元錢,有 種物品,每種一件,每件物品都有對應的價格,并且你對每件物品都賦予了一個重要度(從1~5),求購買的物品的最高性價比和。
注:性價比=物品價格*重要度。

依然是換了個題干的0/1背包模板,只要把價值的計算變成物品價格*重要度即可

)。

1.4.3 裝箱問題 洛谷 P1049

有一個容量為 的箱子,和 個物品,每個物品的體積為整數,裝任意個物品,問剩余的最小體積是多少?

本題中,物品的價值與消費相同,也就是

,代換進原來的代碼即可。

注意最后輸出的是剩余的最小體積,故應當輸出

1.4.4 點菜問題 洛谷 P1164

你有 元錢,有 種菜,每種只有一份,且有對應的價格。要求你把所有的錢都花光,問有多少種不同的點菜方案?

如果只有0元錢,那只有一種點菜方式——啥都不點。

由于問的是方案數而非價值數,所以遞推公式里的

要變成 。

即接下來的遞推公式為

即花

元的點菜方式 = 花( -某道菜價格)元錢的點菜方式的和。

1.4.5 精衛填海 洛谷 P1510

1.4.6 烹飪方案 洛谷 P1417

有 件食材,每件食材有 三個屬性,在 時刻完成一件食材,將得到 的美味度,每件食材所需時間為 。
求在 時間內能得到的最大美味度。

這道題很容易誤解為是裸的0/1背包,但實際上不是。

0/1背包的做法里,不管先裝哪件物品其得到的價值都是不會變的,都是這件物品的價值

但這道題里,先做食材A還是先做食材B,其得到的美味度是不同的,因為美味度與完成時刻

(可理解為背包剩余容量)是有關聯的。此時如果再順序從第1件物品處理到第 件物品,所得到的美味度就不一定是最優解。

所以我們要判斷先做哪一件食材比較好,然后進行排序,最后才是0/1背包的過程。

關于做食材的順序推斷,可以拿相鄰兩件物品進行比較。

假設已經消耗時間

,接下來是第 件物品和第 件物品:

先做

再做 獲得的價值:

先做

再做 獲得的價值:

前者大于后者的條件是,

根據這個條件排序即可。

還要注意,最后的答案并不是

,因為美味度的計算存在減法,到某時刻之后可能所有食材做了都是負美食度的,所以要遍歷 到 。

參考代碼:

#include <bits/stdc++.h> #include <algorithm> #define LL long long//注意數據量較大,不用LL會爆一個測試點拿95分 using namespace std; struct node{LL a;LL b;LL c;}food[100005]; long long bag[100005]; bool cmp(node a,node b) {return a.c*b.b<a.b*b.c; } int main() {int t,n;while(scanf("%d%d",&t,&n)!=EOF){for(int i=0;i<n;i++)scanf("%lld",&food[i].a);for(int i=0;i<n;i++)scanf("%lld",&food[i].b);for(int i=0;i<n;i++)scanf("%lld",&food[i].c);sort(food,food+n,cmp);LL ans=0;for(int i=0;i<n;i++)for(int j=t;j>=food[i].c;j--)bag[j]=max(bag[j],bag[j-food[i].c]+food[i].a-j*food[i].b);for(int i=0;i<=t;i++)ans=max(ans,bag[i]);printf("%lldn",ans);}return 0; }

1.4.7 集合問題 洛谷 P1466

1.4.8 最大約數和 洛谷 P1734

1.4.9 Checkout Assistant CF19B

1.5 關于背包問題與貪心問題的小提醒

如果給的物品可以選擇只拿一部分——比如說一件物品大小為3,價值為3,我可以選擇拿走這件物品的三分之一,其大小為1,價值為1——像這樣的題目,實際上并不是背包問題,而是簡單的貪心問題。

此時只需要算出每種物品的性價比——即價值/大小,然后從性價比最高的開始裝就好了。

二、完全背包

2.1 問題描述

將0/1背包問題中的物品,全部改為可以取無數件,則在這種情況下,求能獲得的最大價值

2.2 解題思路

解題思路與0/1背包完全相同,只是此時因為每件物品都變成了可取無數件,所以需要考慮的不再是取or不取,而是不取or取1件or取2件……直到徹底塞不下為止。

也可以理解為,完全背包問題就是0/1背包問題的變種,只是此時有無數件價值和大小都相同的物品罷了。

在這里,我們不妨回憶一下,在將0/1背包由二維降維到一維時,為了防止——在處理第i件物品時,用到已經取了第i件物品的數據的情況,我們選擇了倒序處理第i件物品。

那么在完全背包中,我們只需要將這一步驟改為正序處理即可,那么在處理大背包時引用到的小背包的數據,就是已經考慮了裝X件該物品的數據了。

如果覺得難以理解,請重新返回到0/1背包降維那一步,好好地理解倒序處理的意義是什么。

2.3 代碼實現

#include <stdio.h> int max(int a,int b) {return a>b?a:b; } int bag[1000];//降維到一維的數組 int w[1000],val[1000];//每件物品的大小與價值 int main() {int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&val[i]);//讀入數據for(int i=1;i<=n;i++) //從第一件物品一直處理到第i件物品for(int j=w[i];j<=m;j++) //改為正序處理,由于僅裝得下時數據才會覆蓋更新,所以從w[i]開始循環bag[j]=max(bag[j],bag[j-w[i]]+val[i]);//此時必定裝得下,于是直接處理,覆蓋上一次處理的數據printf("%dn",bag[m]);//全部處理完后,輸出最后的答案return 0; }

需要注意這里的代碼有了一定的更新優化,內循環直接從

開始,則必定是裝得下第 件物品的,這樣就可以減少一個判斷是否裝得下的步驟,0/1背包問題內的代碼也可以進行同樣的優化。

2.4 剪枝優化

在完全背包問題中,可以采取一個方法來對算法進行優化。

由于每件物品都可以取無數件——所以當存在兩種物品,其中一種物品的價值比另一種低,大小還比另一種大時,我們在循環處理中就可以忽視掉這件物品。

即存在

時,就可以將 物品從序列中刪去。這是結合了貪心算法的剪枝。

但這種剪枝方法對于最差情況是無法優化的,即所有物品的價值、大小都是成梯度的情況,時間復雜度仍然是

。僅對于隨機數據時可以做到較好的優化。

另外需要注意,0/1背包不可使用該優化,因為0/1背包的物品是有限的,若是刪去多種物品可能導致背包空空。

2.5 完全背包相關習題及變種

2.5.1 瘋狂的采藥 洛谷 P1616

題干與采藥問題基本相同,只是每種藥草都可以無限摘取,數據

本題就是個完全背包的裸題,沒有任何變動,唯一需要注意的是數據較大,開二維數組必定爆內存,所以需要降維。

2.5.2 剪緞帶問題 CF189A(在洛谷或CF上都可找到)

有一長度為 的緞帶,要求將其剪成長度為 的小緞帶,且緞帶數量盡可能多,問最多多少條。

同一長度的緞帶可剪出無數條,所以本題實際上是完全背包。但需要注意的是,緞帶必須全部剪成所給的三種長度,也就是說——“背包”必須完全裝滿,不能有剩余的空間——否則剩下來的緞帶,就相當于剪出了一條新長度的緞帶。

這種題型屬于“恰好”背包問題,要求就是必須裝滿。

所以這道題在DP循環時,更新的數據必須是

的倍數或其倍數相加。

處理方法如下:

memset(bag,-1,sizeof(bag)); bag[0]=0; for(int i=0;i<3;i++)for(int j=len[i];j<=n;j++)if(dp[j-bag[i]]!=-1) bag[j]=max(bag[j],bag[j-len[i]]+1);

最開始先將所有大小的背包都初始化為-1。(代表所有大小背包不可更新)

然后將大小為0的背包賦值為0。接下來DP循環中,當且僅當所有剪下的緞帶都是所給長度時,才進行更新。

2.5.3 買干草 洛谷 P2918

2.5.4 數字和數量計算 HDU 1028

完全背包型的點菜問題。可以參考0/1背包點菜問題例題的思路。

2.5.5 質數和分解 洛谷 P2563

三、多維費用背包

3.1 問題描述

此時背包的花費是多維情況,比如說每件物品有一個大小和一個重量,而背包所能裝的大小和重量都有限。其他與完全、0/1背包相同。

3.2 解題思路

費用變成多維,那么狀態也變成多維即可。

假設有二維費用,那么就設

來存儲在背包所裝物品大小不超過v,重量不超過w時的最大價值。

只需要在循環求解時,多加上一層循環即可,兩層循環一層來考慮

,一層來考慮 。

3.3 代碼實現

(這里的代碼以0/1二維背包為例,完全背包或是更多維費用參考上述進行改造即可)

#include <stdio.h> #include <stdlib.h> #include <string.h> int vi[50],wi[50],k[50]; int bag[400][400]; int max(int a,int b) {return a>b?a:b; } int main() {int v,w,n;while(scanf("%d%d%d",&v,&w,&n)!=EOF)//最大可裝體積,最大可裝重量,物品件數{memset(bag,0,sizeof(bag));for(int i=1;i<=n;i++)scanf("%d%d%d",&vi[i],&wi[i],&k[i]);for(int i=1;i<=n;i++)for(int j=w;j>=wi[i];j--)//一層循環考慮重量for(int l=v;l>=vi[i];l--)//一層循環考慮體積bag[j][l]=max(bag[j][l],bag[j-wi[i]][l-vi[i]]+k[i]);printf("%dn",bag[w][v]);}return 0; }

3.4 多維費用的例題

3.4.1 NASA的食物計劃 洛谷 P1507

這題就是裸的二維費用0/1背包,上述代碼直接復制就可AC。

3.4.2 限制件數的背包問題 原創

有一個大小為 的背包,以及若干件物品。每件物品都有對應的價值與大小。
背包最多只允許裝 件物品,且所裝物品總大小不能超過背包容量。
問所裝物品的最大價值是多少?

這題看上去像是一個裸的0/1背包,只是加上了一個“最多允許裝

件物品”的限制,但是換個角度上來看,這題就是一道標準的二維費用0/1背包。

每件物品多了一種花費“件數”,且每件物品該花費的值都是1。最多允許的花費為

表示所裝物品不超過 件,且總體積不大于 時的最大價值。然后老套路寫就可以了。

3.4.3 找女朋友問題 洛谷 P1509

四、多重背包

4.1 問題描述

在0/1背包的基礎上加上一個條件:此時每種物品不再只有一件,而是最多有 件可用。

4.2 解題思路

其實就是在0/1背包的代碼基礎上再多加入一層循環:每件物品都從裝1件到裝

件進行測試,如果當前背包能裝得下,就更新當前背包的價值。

需要注意,處理時依然是倒序處理,正序處理的話就變成完全背包而不符題意了。

還需要注意,在循環更新價值時,物品的價值要記得乘上數量,因為價值只是每一件物品的價值。

PS:多重背包還可以運用單調隊列進行進一步優化,其最終的時間復雜度可以縮減到

4.3 代碼實現

#include <cstdio> #include <algorithm> //使用c++的標準庫,內置max函數 int bag[1000];//已經降維為一維的背包 int w[1000],val[1000],num[1000];//每件物品的大小與價值、最多能裝的數量 int main() {int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++)scanf("%d%d%d",&w[i],&val[i],&num[i]);//讀入數據for(int i=1;i<=n;i++) //從第一件物品一直處理到第i件物品for(int j=m;j>=w[i];j--) //倒序處理for(int l=1;l<=num[i];l++)//多了一層循環if(j>=l*w[i])//裝得進去l件物品的話bag[j]=max(bag[j],bag[j-w[i]*l]+val[i]*l);//就判斷是塞還是不塞比較好,注意乘lprintf("%dn",bag[m]);//全部處理完后,輸出最后的答案return 0; }

4.4 多重背包的例題

4.4.1 選課時間 HDU 2079

一道多重背包的模板題,不過差別在于求的不是最大價值而是方法數,具體解法可以參考洛谷的P1164。

4.4.2 買米問題 HDU 2191

4.4.3 擺花 洛谷 P1077

五、分組背包

5.1 問題描述

在分組背包問題中, 件物品被分為了 組,其中每組內的物品最多只能取一件,求所能拿的最大價值。

5.2 解題思路

我們可以建立一個二維數組,其中一維表示組別,另外一維則表示該物品為其所在組中的第幾件。也可以通過其他的方案,將同一組別的物品放在一起。

其他的處理方法,基本與0/1背包沒有較大的差別。

具體看下方代碼。

5.3 代碼實現

#include <stdio.h> #include <algorithm> #include <string.h> using namespace std; struct node{int w;int val;int t;}art[1005]; int num[105]; int dp[1005]; int cmp(node a,node b) {return a.t<b.t; } int main() {int n,m;while(scanf("%d%d",&m,&n)!=EOF)//n件物品,背包大小為m{int k=0;for(int i=0;i<n;i++){scanf("%d%d%d",&art[i].w,&art[i].val,&art[i].t);//物品的大小、價值、組別num[art[i].t]++;//該組別所含物品數增多k=max(k,art[i].t);//判定有多少個組}sort(art,art+n,cmp);//按照組別進行排序,同組的排在一起int p=0,q;//用于順序處理的指針for(int i=1;i<=k;i++)//k個組{for(int j=m;j>=0;j--)//01背包的倒序處理方案{q=p;for(int l=1;l<=num[i];l++)//每個組有num[i]件物品{if(j>=art[q].w)dp[j]=max(dp[j],dp[j-art[q].w]+art[q].val);//這種處理方案能保證一組里只取一件q++;}if(j==0) p=q;}}printf("%dn",dp[m]);} }

這里以洛谷的P1757為例。

5.4 分組背包的例題

5.4.1 排兵布陣 洛谷 P5322

一道分組背包的變式題,稍有難度。

本質上就是

座城堡 個組別,每個組別 件物品,當然,這里需要預處理,將每個組別內的物品價值進行處理計算使其符合預期,然后就是分組背包的處理方案了。

總結

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

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