【模板】各種背包問題&講解
背包問題集合
一般來說,動態規劃(DP)都是初學者最難闖過的一關,而在這裏詳細解說動態規劃的一種經典題型:背包問題。
這裏介紹的背包分為以下幾種:01背包,完全背包,多重背包,混合背包,二維費用的背包。(以後會持續更新)
【一:01背包】
首先放上例題:
01背包問題
【題目描述】:
一個旅行者有一個最多能裝M公斤的背包,現在有n件物品,他們的重量分別是W1,W2…Wn,它們的價值分別是C1,C2……Cn,求旅行者能夠獲得的最大總價值。
【輸入格式】:
第一行:兩個整數,M,(背包容量,M<=200)和N(物品數量N<=30)
第2至N+1行,每行兩個整數,Wi,Ci,表示每個物品的重量和價值。
【輸出格式】:僅一行,一個數,表示最大總價值。
【輸入樣例#1】:
10 4
2 1
3 3
4 5
7 9
【輸出樣例#1】:12
【輸入樣例#2】:
8 4
5 6
4 2
1 2
01背包問題可以說是最簡單的背包問題,簡單之處就在:他的每一個物品都只有一個。
首先定義一個f[MAXN][MAXN]數組,用來記錄最大價值。即:f[i][v]表示的就是當前i件物品放入一個容量為v的背包的時候可以獲得的最大價值。
01背包的狀態轉移方程式便是:f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i])。
眾所周知DP問題最重要的便是狀態轉移方程式了,那麽這個狀態轉移方程式究竟是怎麽來的呢??
詳解來啦“!!!
既然說了是”將第i件物品放入背包“,那麽如果只考慮第i件物品的方式策略,那麽就只和第i-1件物品有關了,如果是放第i件物品,那麽問題就轉化為:”前i-1件物品放入容量為v的背包中“,此時能夠獲得的最大價值就是f[i-1][v-w[i]],也就是第i-1件物品放入容量為v(原來的總容量)減去w[i](第i件物品的占容)產生的最優價值,再加上放通過入第i件物品增加的價值c[i]。
那麽放入第i件物品產生的最大價值就是要在”放“,或者是”不放“中選擇了,”不放“的話,產生的價值就是f[i-1][v]
若物品數量為n,背包總容量為m,那麽循環到最後,答案也就是f[n][m]啦。
那麽附上代碼:::
#include<bits/stdc++.h>
#define MAXN 0x7ff
using namespace std;
int m,n,w[MAXN],c[MAXN];
int f[MAXN][MAXN],i,j;//f[i][v]表示第i件物品放入容量為v的背包所能產生的最大價值。
int maxn(int x,int y)//比較,輸出最大值
{
if(x>y)
return x;
else
return y;
}
int main()
{
cin>>m>>n;
for(i=1;i<=n;i++)
cin>>w[i]>>c[i];//輸入不解釋
for(i=1;i<=n;i++)
for(j=m;j>0;j--)
{
if(w[i]<=j)
f[i][j]=maxn(f[i-1][j],f[i-1][j-w[i]]+c[i]);//狀態轉移方程式。
else f[i][j]=f[i-1][j];
}
cout<<f[n][m];
return 0;
}
好了以上就是最簡單的01背包模板了qwq。
【二:完全背包問題】
還是例題打頭陣。
完全背包問題
【題目描述】
設有n種物品,每種物品有一個價值,但每種物品的數量是無限的,同時有一個背包,最大承載量微m,今從n種物品中選取若幹件,(同一種物品可以多次選舉)使其重量的和小於等於m,而且價值的和最大。
【輸入】共N+1行
第一行:兩個整數:M(背包容量M<=200)和N(物品數量,N<=30);
第二行至第N+1行,每行兩個整數,Wi,Ci,表示每個物品的重量和價值。
【輸出】
近一行:一個數,表示最大的價值;
【輸入樣例】
10 4
2 1
3 3
4 5
7 9
【輸出樣例】
12
有的小夥伴們可能一看到例題就會發現一個與01背包不同的地方:每種物品的數量是無限多的。沒錯,這就是完全背包問題。其實這個問題沒有比01背包難多少,只是不是很好想。
既然每種物品所取的數量可能是1,2,3.......,所以與它相關的策略便不是取或者不取的問題了,而是,取不取,取多少的問題了。既然同樣是背包問題,那麽我們可不可以考慮延續01背包的方法來做呢,答案是Yes。其實與01背包不同的地方就是只有放進去的每種物品的件數不一定是1就是了,那麽只要多一重關於每種物品取多少的循環不就可以了嗎。相對的,其狀態轉移方程式也要略有改動,變為:f[i][v]=max(f[i-1][v-k*w[i]]+k*c[i],f[i-1][v]),可見就是比01背包多了一個k而已,而這個k就是循環的這第i種物品取的件數。
現在解釋一下原因:首先我們想一下為甚麽在上題中的01背包代碼中第二重循環是for(int j=m;j>=0;j--)而不是for(int j=1;j<+m;j++)呢??(你就沒有懷疑過嗎??~~~)
Because~因為:要保證第i次循環裏的狀態f[i][v]是由狀態f[i][[v-w[i]]遞推而來的,也就是為了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時,依據的是一個絕不可能選入第i種物品的子結果f[i][v-w[i]]。如果如果還不是很明白,大家可以畫一個表格來自己推一下,看看逆序和正序時得到的結果有什麽不同。。。。。。
好了重點來了!! : 現在大家已經知道了01背包的順序,那麽現在問題來了:完全背包的順序呢??
考慮“加選一件第i種物品”這種策略時,卻正需要一個可能已經選入第i種物品的子結果f[i][v=w[i]],所以就可以並且必須采用v=0,.....v的順序循環,這就是則個簡單的程序何以成立的原因啦。
然後給大家附上代碼:: //不準抄襲!! 好吧像我這種蒟蒻代碼有誰會抄呢 呵呵~~
#include<iostream> #include<cstdio> using namespace std; const int MAXN=31,MAXM=201; int m,n,w[MAXN],c[MAXN],f[MAXN][MAXM]; int main() { scanf("%d%d",&m,&n); for(int i=1;i<=n;i++) scanf("%d%d",&w[i],&c[i]); for(int i=1;i<=n;i++) for(int v=1;v<=m;v++) if(v<w[i]) f[i][v]=f[i-1][v]; else {if(f[i-1][v]>f[i][v-w[i]]+c[i]) f[i][v]=f[i-1][v]; else f[i][v]=f[i][v-w[i]]+c[i];} printf("%d",f[n][m]); return 0; }//這個碼風不是很好看,大家看不慣可以改一改......
【三:多重背包問題】
好了依然是看例題::
模板例題:慶功會
【題目描述】
設有n種物品,每種物品有一個價值,但每種物品的數量是有限的,同時有一個背包,最大承載量微m,今從n種物品中選取若幹件,(同一種物品可以多次選舉)使其重量的和小於等於m,而且價值的和最大。
【輸入】共N+1行
第一行:兩個整數:N(物品數量,N<=30)和M(背包容量M<=200)
第二行至第N+1行,每行兩個整數,Wi,Ci,Si,分別表示每個物品的重量、價值、數量。
【輸出】
近一行:一個數,表示最大的價值;
【輸入樣例】
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
【輸出樣例】
1040
這個題目就又不一樣了,因為題目中說的每一件物品的件數是“有限的”,也就是說可以是1、2......但也不是無限。
這個看起來可能就比較e xin,但是如果仔細想一想,其實也並不是很難。
(蒟蒻認為的)重點詳解:
做法1:和完全背包接軌:完全背包裏面有一個k,用來表示物品的個數,這裏面也是一樣的,因為對於第i種物品,我們可以設置他有n[i]+1種取數策略,:取0、1.....n[i]件,so我們再次設置一個f[i][v]表示前i種物品放入一個容量為v的背包所能產生的最大價值,那麽:f[i][v]=max(f[i-1)[v-k*w[i]]+k*c[i],f[i-1][v]),那麽復雜度就是O(V*Σn[i])。
顯然,上方做法時間復雜度太高。於是就有了第二個方法。
做法2:和01背包接軌。大家可能都覺得完全背包比01背包更要高大上一些,但是很不幸,做法2是要遠遠優於做法1的。先給大家一個引路思想,第i件物品有n[i]的數量,那麽是不是可以把它轉化為n個數量只有一個的物品呢??看到這裏,你恍然大悟:哦,好巧妙。
而事實上,如果你只想到這裏,就開始興奮無腦地開始打代碼的話,你就會呵呵進化成神犇了。。。。。你您可以算一算如果這樣做的話時間復雜度是多少:O(V*Σn[i]) ............
好極了,可是我並沒有在逗你啊。。。。。
那麽接下來就是優化了,在這裏,我們考慮二進制優化。
方法:將第i件物品分成若幹件物品,每個物品有一個系數l,這件物品的費用和價值軍事原來的費用和價值乘以l,使這些系數分別為1,2,4...2(k-1)余出來的再拿出來就是n[i]-2(k+1),且k是滿足n[i]-2(k+1)>0的最大整數。(例:13分為1,2,4,6)。
這樣就將第i件物品分成了O(logn[i)種物品,將時間復雜度降低成為了O(V*Σlogn[i])的01背包問題,改進是不是很大呢?
下面是代碼:
#include<iostream>
#include<cstdio>
#define MAXN 10001
#define MAXM 6001
using namespace std;
int v[MAXN],w[MAXN],f[MAXM];
int n,m,n1;
int max(int a,int b)
{
if(a>b) return a;
else return b;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
int x,y,s,t=1;
scanf("%d%d%d",&x,&y,&s);
while(s>=t)
{
v[++n1]=x*t;
w[n1]=y*t;
s-=t;
t*=2;
}
v[++n1]=x*s;
w[n1]=y*s;
}
for(int i=1;i<=n1;i++)
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
printf("%d\n",f[m]); return 0;
}
【四:混合三種背包問題】
大概很多人一看到這個就淚奔了,對,沒錯,就是把01、完全、多重背包混合起來考你。你可能肯定覺得很惡心,覺得難極了,確實,對於沒有學過前幾種背包的人來說,這是一個很大的挑戰,但是不要忘了,我們已經學完了呀!所以,我哦們就可以很輕易地將這一個難題轉化為極個簡單的子題了!!
其實也不用怎麽解釋了,先判斷該物品是不是屬於完全背包,if(YES)->用完全背包去做,if(NOT)->用混合背包去做(因為01背包本身即是多重背包的一個樣例,用多重背包做完全沒有問題的。)
好了下面附上代碼:
#include<iostream>
#include<cstdio>
using namespace std;
int m,n,w[31],c[31],p[31],f[201];
int max(int a,int b)
{
if(a>b) return a;
else return b;
}
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&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[i]]+c[i]);
}
else
{
for(int j=1;j<p[i];j++)
for(int k=m;k>=w[i];k--)
f[k]=max(f[k],f[k-w[i]]+c[i]);
}
printf("%d",f[m]);
return 0;
}
好了,關於背包問題就先說到這裏(持續更新。。。),如果有不明白個或者錯誤的地方可以給我留言。。。
【模板】各種背包問題&講解