01背包:
每件物品都有它的價值和體積,你的背包有一定容量,如何能獲取最大價值?
第一行有2個整數分別表示容量和物品數(n)
接下來n行每兩個數個分別代表一個物體的體積和價值
很顯然,每種物品只能拿一件
當然你也可以不拿
如果拿(前提是有足夠空間),就相當於背包少了v[i]的體積,多了c[i]的價值,
不拿的話(沒有空間)就是上一個物品占用當時最大的體積的價值
一層循環枚舉每一個物品
二層循環枚舉可用的最大體積(為什么這么說呢?,因為一個物體的體積如果是5,那么只剩2肯定裝不下)
如果能裝下(c代表價值v代表枚舉體積)
f[i][v]=max(f[i-1][v],f[i-1][v-w[i]]+c[i]);
否則
f[i][v]=f[i-1][v];//只能跟前面一樣
注意,枚舉的v是指最大占用v體積的情況下獲得最大的價值
v一定要倒着枚舉(從最大體積開始到1結束)
因為01背包只能選一個,你在選擇v-w[i]時,f[i-1][v-w[i]]其中一定沒有選過i,應該很容易理解吧.
這時dalao們就發現問題了,若有5000件物品背包容量是100000,就爆空間了,這怎么辦呢?
我們可以想一個問題,如果用15的空間既可以獲得10的價值也可以獲得50的價值,那么你選哪個?
都選50的吧
那么獲得10價值的方案還有用嗎?
顯然沒有用了
所以我們只需要記錄每一個容積所能獲得的最大價值就好了,這樣就可以壓縮成為一維,從而舍棄第一維,只要容積那一維(只儲存最大的價值)
一層循環枚舉每一個物品
二層循環枚舉可用的最大體積
如果能裝下
f[v]=max(f[v],f[v-w[i]]+c[i]);
否則//自己等於自己,當然沒必要寫這句
完全背包:
每件物品都有它的價值和體積但每件物品可以無限拿,你的背包有一定容量,如何能獲取最大價值?
第一行有2個整數分別表示容量和物品數(n)
接下來n行每兩個數個分別代表一個物體的體積和價值
很顯然,每種物品可以拿無數件
當然你也可以不拿
如果拿(前提是有足夠空間),就相當於背包少了v[i]的體積,多了c[i]的價值,
不拿的話(沒有空間)就是上一個物品占用當時最大的體積的價值
我們先講一種便於理解的方法,即:
加一重循環枚舉個數k
當然,依然不能超出背包限制
狀態轉移方程f[v] = max{ f[v],f[ j - k*v[i]]+k* c[i]};
這應該每位dalao和像我一樣的蒟蒻都能明白吧
然后明白了就可以上一種玄學一的點了
一層循環枚舉每一個物品
二層循環枚舉可用的最大體積
如果能裝下(c代表價值v代表枚舉體積)
f[v]=max(f[v],f[v-w[i]]+c[i]);
注意,枚舉的v是指最大占用v體積的情況下獲得最大的價值
v一定要正着枚舉(從1開始到最大體積結束)
因為完全背包可以無限選,你在選擇v-w[i]時,他就有可能已經選過了.
到這里有些像我一樣的蒟蒻就不懂了
我舉一個生動形象的例子
背包容量為5
有1個物品,體積為2,價值為3(這么簡單的例子不是因為我懶)
從5開始呢?
f[5]=f[5-2]+3;
f[4]=f[4-2]+3;
f[3]=f[3-2]+3;
f[2]=f[2-2]+3;
1和0不行
可以看出,從5到2,關系分別是
由3推5
由2推4
由1推3
由0推2
從一開始推出來的5開始沒有被用過的再被利用
從1開始呢?
1和0不行
f[2]=f[2-2]+3;
f[3]=f[3-2]+3;
f[4]=f[4-2]+3;
f[5]=f[5-2]+3;
可以看出,從5到2,關系分別是
由0推2
由1推3
由2推4
由3推5
可以看出:
由0推出的2又在推4的時候用到了,這就實現了完全背包
大家應該可以發現我在寫01背包和完全背包時有很多相同之處
這是因為01背包是一種特殊的完全背包
好啦,一篇博客結束啦
emmm...怎么能結束了呢,我又來更新博客啦,
二維費用背包,
即有兩個限制,比如,一件物品既有體積又有質量,你不能超出2者其中一個,求最大價值
明白了01背包和完全背包之后,這個問題就簡單許多
假如一種限制也沒有呢?
代碼應該是
for(k=1; k<=c; k++) { ans=max(a[k]+ans,ans); }
加一種限制呢?(即只有體積限制)
for(k=1; k<=c; k++) { for(i=n; i>=v[k]; i--) { a[i] = max(a[i-v[k]]+jz[k],a[i][j]); } }
區別是什么?
加了一層循環,又加了一維的空間
由此推出
for(k=1; k<=c; k++) { for(i=n; i>=v[k]; i--) { for(j=m; j>=w[k]; j--) { a[i][j] = max(a[i-v[k]][j-w[k]]+jz[k],a[i][j]); } } }
合情合理又簡單.
多重背包問題
即一個物品可以拿指定的次數(這才是正常的日常問題)
還是先考慮簡單的情況
轉換01背包(硬轉換)
再加一層循環枚舉個數,不能超過背包容量
for(k=1; k<=c; k++) { for(i=n; i>=v[k]; i--) { for(j=1; j<=c[i]; j++) { a[i] = max(a[i-j*w[k]]+j*jz[k],a[i]); } } }
這樣就連我這樣的蒟蒻也明白吧
接下來就要上一種更厲害的方法了
3=1+2
這個式子每個人都會吧
轉換2進制 11=1+10
那若給你1,10,100,1000,10000.....
這些二進制數,是不是能湊出所有的數呢?
任何一個二進制數每一位要么0,要么1,所以都能湊出來
舉個栗子
假如二進制第3位是1那么就加上1000,如果是0就不加
好啦回歸正題
1,10,100,1000,10000.....
轉換10進制: 1,2,4,8....
也就是說,我們不需要每個都枚舉,假如有一個物品可以拿10個,那我們只需要將1,2,4,3這幾種情況跑一遍01就好了
可能還是有像我這樣的蒟蒻看不懂,我來證明一下(看的別的dalao的證明方法)
如果我們要湊的數n小於等於2^k(k是能拆最大的二進制數 以10為例,k就是2)轉換二進制直接湊都明白吧
如果n大於2^k怎么辦呢?
我們先想一個問題,假如你有2這個數,並且能湊出4這個數,那么是不是也能湊出4+2呢?
如果要湊n,我們可以先湊n-2^k,n-2^k肯定能湊出,而2^k也能湊出,那么n就能湊出
轉換部分
for(i=1; i<=n; i++) { scanf("%d%d%d",&w[i],&v[i],&s); for(j=1; j<=s; j*=2) { w[++k] = w[i]*j; v[k] = v[i]*j; s-=j; } if(0!=s) { w[++k] = w[i]*s; v[k] = v[i]*s; } }
分組背包:
有好多組物品,每組只能拿一個物品,使總價值最大
其實這個很簡單,只需要再加一層循環枚舉組,使組內物品在不超出當前背包容積的情況下價值最大就好啦
int zd2=0; for(i=1; i<=zd; i++) {//枚舉組 for(j=m; j>=1; j--) {//枚舉空間 for(p=b[1].k ; p>=1; p--) {//枚舉組內每一物品 if(w[b[i].bb[p]]<=j){//如果能裝下 if(zd2<a[j-w[b[i].bb[p]]]+v[b[i].bb[p]])//找最大的 zd2=a[j-w[b[i].bb[p]]]+v[b[i].bb[p]]; } } a[j]=max(a[j],zd2);//在和原先的比較就好了 zd2=0; } }
注意:最好不要在找最大時就對a[]做手腳,否則可能出錯(可以自己想下為什么)
當然,大家看到這個代碼肯定一臉懵,我在講一下預處理
這是定義的結構體
struct po { int bb[1001];//存放組內每一個物品的序號 int k;//有多少物品(組內) }; po b[101]= {0};//組
輸入部分:
for(i=1; i<=n; i++) { scanf("%d%d%d",&w[i],&v[i],&zu); b[zu].bb[++b[zu].k] = i;//組內下一個物品編號 if(zd<zu)//找出有多少組 zd=zu; }
更新完啦!!!
時隔幾個月,我又來更新了....
至於樹形背包,前提是先學會分組背包
以洛谷p2014選課為例
設dp[x][i]為以x為根的子樹,花i代價最大的價值,對於x的子樹都是葉子節點的情況應該很好理解
大家可以手動畫一下圖,這種情況很簡單,但問題是一般情況我們要解決的並不是這種問題
假設我們正在計算dp[x][k],樹根的費用是m
我們必須要選樹根,因為樹根不選是無法選兒子的
那么我們只需要計算出所有子樹中加起來費用是k - m的最大價值