P01: 01背包問題
題目:有N件物品和一個(gè)容量為V的背包。第i件物品的費(fèi)用是c[i],價(jià)值是w[i]。求解將哪些物品裝入背包可使這些物品的費(fèi)用總和不超過背包容量,且價(jià)值總和最大。
基本思路:這是最基礎(chǔ)的背包問題,特點(diǎn)是:每種物品僅有一件,可以選擇放或不放。
用子問題定義狀態(tài):即f[i][v]表示前i件物品恰放入一個(gè)容量為v的背包可以獲得的最大價(jià)值。則其狀態(tài)轉(zhuǎn)移方程便是:f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。
這個(gè)方程非常重要,基本上所有跟背包相關(guān)的問題的方程都是由它衍生出來的。所以有必要將它詳細(xì)解釋一下:“將前i件物品放入容量為v的背包中”這個(gè)子問題,若只考慮第i件物品的策略(放或不放),那么就可以轉(zhuǎn)化為一個(gè)只牽扯前i-1件物品的問題。如果不放第i件物品,那么問題就轉(zhuǎn)化為“前i-1件物品放入容量為v的背包中”;如果放第i件物品,那么問題就轉(zhuǎn)化為“前i-1件物品放入剩下的容量為v-c[i]的背包中”,此時(shí)能獲得的最大價(jià)值就是f [i-1][v-c[i]]再加上通過放入第i件物品獲得的價(jià)值w[i]。
注意f[i][v]有意義當(dāng)且僅當(dāng)存在一個(gè)前i件物品的子集,其費(fèi)用總和為v。所以按照這個(gè)方程遞推完畢后,最終的答案并不一定是f[N] [V],而是f[N][0..V]的最大值。如果將狀態(tài)的定義中的“恰”字去掉,在轉(zhuǎn)移方程中就要再加入一項(xiàng)f[i][v-1],這樣就可以保證f[N] [V]就是最后的答案。至于為什么這樣就可以,由你自己來體會了。
優(yōu)化空間復(fù)雜度
以上方法的時(shí)間和空間復(fù)雜度均為O(N*V),其中時(shí)間復(fù)雜度基本已經(jīng)不能再優(yōu)化了,但空間復(fù)雜度卻可以優(yōu)化到O(V)。
先考慮上面講的基本思路如何實(shí)現(xiàn),肯定是有一個(gè)主循環(huán)i=1..N,每次算出來二維數(shù)組f[i][0..V]的所有值。那么,如果只用一個(gè)數(shù)組f [0..V],能不能保證第i次循環(huán)結(jié)束后f[v]中表示的就是我們定義的狀態(tài)f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1] [v-c[i]]兩個(gè)子問題遞推而來,能否保證在推f[i][v]時(shí)(也即在第i次主循環(huán)中推f[v]時(shí))能夠得到f[i-1][v]和f[i-1][v -c[i]]的值呢?事實(shí)上,這要求在每次主循環(huán)中我們以v=V..0的順序推f[v],這樣才能保證推f[v]時(shí)f[v-c[i]]保存的是狀態(tài)f[i -1][v-c[i]]的值。偽代碼如下:
for i=1..N for v=V..0 f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相當(dāng)于我們的轉(zhuǎn)移方程f[i][v]=max{f[i-1][v],f[i- 1][v-c[i]]},因?yàn)楝F(xiàn)在的f[v-c[i]]就相當(dāng)于原來的f[i-1][v-c[i]]。如果將v的循環(huán)順序從上面的逆序改成順序的話,那么則成了f[i][v]由f[i][v-c[i]]推知,與本題意不符,但它卻是另一個(gè)重要的背包問題P02最簡捷的解決方案,故學(xué)習(xí)只用一維數(shù)組解01背包問題是十分必要的。
總結(jié):01背包問題是最基本的背包問題,它包含了背包問題中設(shè)計(jì)狀態(tài)、方程的最基本思想,另外,別的類型的背包問題往往也可以轉(zhuǎn)換成01背包問題求解。故一定要仔細(xì)體會上面基本思路的得出方法,狀態(tài)轉(zhuǎn)移方程的意義,以及最后怎樣優(yōu)化的空間復(fù)雜度。
P02: 完全背包問題
題目:有N種物品和一個(gè)容量為V的背包,每種物品都有無限件可用。第i種物品的費(fèi)用是c[i],價(jià)值是w[i]。求解將哪些物品裝入背包可使這些物品的費(fèi)用總和不超過背包容量,且價(jià)值總和最大。
基本思路:這個(gè)問題非常類似于01背包問題,所不同的是每種物品有無限件。也就是從每種物品的角度考慮,與它相關(guān)的策略已并非取或不取兩種,而是有取0件、取1件、取2件……等很多種。如果仍然按照解01背包時(shí)的思路,令f[i][v]表示前i種物品恰放入一個(gè)容量為v的背包的最大權(quán)值。仍然可以按照每種物品不同的策略寫出狀態(tài)轉(zhuǎn)移方程,像這樣:f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<= v}。這跟01背包問題一樣有O(N*V)個(gè)狀態(tài)需要求解,但求解每個(gè)狀態(tài)的時(shí)間則不是常數(shù)了,求解狀態(tài)f[i][v]的時(shí)間是O(v/c[i]),總的復(fù)雜度是超過O(VN)的。
將01背包問題的基本思路加以改進(jìn),得到了這樣一個(gè)清晰的方法。這說明01背包問題的方程的確是很重要,可以推及其它類型的背包問題。但我們還是試圖改進(jìn)這個(gè)復(fù)雜度。
完全背包問題有一個(gè)很簡單有效的優(yōu)化,是這樣的:若兩件物品i、j滿足c[i]<=c[j]且w[i]>=w[j],則將物品j去掉,不用考慮。這個(gè)優(yōu)化的正確性顯然:任何情況下都可將價(jià)值小費(fèi)用高得j換成物美價(jià)廉的i,得到至少不會更差的方案。對于隨機(jī)生成的數(shù)據(jù),這個(gè)方法往往會大大減少物品的件數(shù),從而加快速度。然而這個(gè)并不能改善最壞情況的復(fù)雜度,因?yàn)橛锌赡芴貏e設(shè)計(jì)的數(shù)據(jù)可以一件物品也去不掉。
既然01背包問題是最基本的背包問題,那么我們可以考慮把完全背包問題轉(zhuǎn)化為01背包問題來解。最簡單的想法是,考慮到第i種物品最多選V/c [i]件,于是可以把第i種物品轉(zhuǎn)化為V/c[i]件費(fèi)用及價(jià)值均不變的物品,然后求解這個(gè)01背包問題。這樣完全沒有改進(jìn)基本思路的時(shí)間復(fù)雜度,但這畢竟給了我們將完全背包問題轉(zhuǎn)化為01背包問題的思路:將一種物品拆成多件物品。
更高效的轉(zhuǎn)化方法是:把第i種物品拆成費(fèi)用為c[i]*2^k、價(jià)值為w[i]*2^k的若干件物品,其中k滿足c[i]*2^k<V。這是二進(jìn)制的思想,因?yàn)椴还茏顑?yōu)策略選幾件第i種物品,總可以表示成若干個(gè)2^k件物品的和。這樣把每種物品拆成O(log(V/c[i]))件物品,是一個(gè)很大的改進(jìn)。但我們有更優(yōu)的O(VN)的算法。 * O(VN)的算法這個(gè)算法使用一維數(shù)組,先看偽代碼: <pre class"example"> for i=1..N for v=0..V f[v]=max{f[v],f[v-c[i]]+w[i]};
你會發(fā)現(xiàn),這個(gè)偽代碼與P01的偽代碼只有v的循環(huán)次序不同而已。為什么這樣一改就可行呢?首先想想為什么P01中要按照v=V..0的逆序來循環(huán)。這是因?yàn)橐WC第i次循環(huán)中的狀態(tài)f[i][v]是由狀態(tài)f[i-1][v-c[i]]遞推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮“選入第i件物品”這件策略時(shí),依據(jù)的是一個(gè)絕無已經(jīng)選入第i件物品的子結(jié)果f[i-1][v-c[i]]。而現(xiàn)在完全背包的特點(diǎn)恰是每種物品可選無限件,所以在考慮“加選一件第i種物品”這種策略時(shí),卻正需要一個(gè)可能已選入第i種物品的子結(jié)果f[i][v-c[i]],所以就可以并且必須采用v= 0..V的順序循環(huán)。這就是這個(gè)簡單的程序?yàn)楹纬闪⒌牡览怼?/p>
這個(gè)算法也可以以另外的思路得出。例如,基本思路中的狀態(tài)轉(zhuǎn)移方程可以等價(jià)地變形成這種形式:f[i][v]=max{f[i-1][v],f[i][v-c[i]]+w[i]},將這個(gè)方程用一維數(shù)組實(shí)現(xiàn),便得到了上面的偽代碼。
總結(jié):完全背包問題也是一個(gè)相當(dāng)基礎(chǔ)的背包問題,它有兩個(gè)狀態(tài)轉(zhuǎn)移方程,分別在“基本思路”以及“O(VN)的算法“的小節(jié)中給出。希望你能夠?qū)@兩個(gè)狀態(tài)轉(zhuǎn)移方程都仔細(xì)地體會,不僅記住,也要弄明白它們是怎么得出來的,最好能夠自己想一種得到這些方程的方法。事實(shí)上,對每一道動(dòng)態(tài)規(guī)劃題目都思考其方程的意義以及如何得來,是加深對動(dòng)態(tài)規(guī)劃的理解、提高動(dòng)態(tài)規(guī)劃功力的好方法。
P03: 多重背包問題
題目:有N種物品和一個(gè)容量為V的背包。第i種物品最多有n[i]件可用,每件費(fèi)用是c[i],價(jià)值是w[i]。求解將哪些物品裝入背包可使這些物品的費(fèi)用總和不超過背包容量,且價(jià)值總和最大。
基本算法:這題目和完全背包問題很類似?;镜姆匠讨恍鑼⑼耆嘲鼏栴}的方程略微一改即可,因?yàn)閷τ诘趇種物品有n[i]+1種策略:取0件,取1件……取 n[i]件。令f[i][v]表示前i種物品恰放入一個(gè)容量為v的背包的最大權(quán)值,則:f[i][v]=max{f[i-1][v-k*c[i]]+ k*w[i]|0<=k<=n[i]}。復(fù)雜度是O(V*∑n[i])。
另一種好想好寫的基本方法是轉(zhuǎn)化為01背包求解:把第i種物品換成n[i]件01背包中的物品,則得到了物品數(shù)為∑n[i]的01背包問題,直接求解,復(fù)雜度仍然是O(V*∑n[i])。
但是我們期望將它轉(zhuǎn)化為01背包問題之后能夠像完全背包一樣降低復(fù)雜度。仍然考慮二進(jìn)制的思想,我們考慮把第i種物品換成若干件物品,使得原問題中第i種物品可取的每種策略——取0..n[i]件——均能等價(jià)于取若干件代換以后的物品。另外,取超過n[i]件的策略必不能出現(xiàn)。
方法是:將第i種物品分成若干件物品,其中每件物品有一個(gè)系數(shù),這件物品的費(fèi)用和價(jià)值均是原來的費(fèi)用和價(jià)值乘以這個(gè)系數(shù)。使這些系數(shù)分別為 1,2,4,...,2^(k-1),n[i]-2^k+1,且k是滿足n[i]-2^k+1>0的最大整數(shù)。例如,如果n[i]為13,就將這種物品分成系數(shù)分別為1,2,4,6的四件物品。
分成的這幾件物品的系數(shù)和為n[i],表明不可能取多于n[i]件的第i種物品。另外這種方法也能保證對于0..n[i]間的每一個(gè)整數(shù),均可以用若干個(gè)系數(shù)的和表示,這個(gè)證明可以分0..2^k-1和2^k..n[i]兩段來分別討論得出,并不難,希望你自己思考嘗試一下。
這樣就將第i種物品分成了O(log n[i])種物品,將原問題轉(zhuǎn)化為了復(fù)雜度為O(V*∑log n[i])的01背包問題,是很大的改進(jìn)。
O(VN)的算法:多重背包問題同樣有O(VN)的算法。這個(gè)算法基于基本算法的狀態(tài)轉(zhuǎn)移方程,但應(yīng)用單調(diào)隊(duì)列的方法使每個(gè)狀態(tài)的值可以以均攤O(1)的時(shí)間求解。由于用單調(diào)隊(duì)列優(yōu)化的DP已超出了NOIP的范圍,故本文不再展開講解。
小結(jié):這里我們看到了將一個(gè)算法的復(fù)雜度由O(V*∑n[i])改進(jìn)到O(V*∑log n[i])的過程,還知道了存在應(yīng)用超出NOIP范圍的知識的O(VN)算法。希望你特別注意“拆分物品”的思想和方法,自己證明一下它的正確性,并用盡量簡潔的程序來實(shí)現(xiàn)。
P04: 混合三種背包問題
問題:如果將P01、P02、P03混合起來。也就是說,有的物品只可以取一次(01背包),有的物品可以取無限次(完全背包),有的物品可以取的次數(shù)有一個(gè)上限(多重背包)。應(yīng)該怎么求解呢?
考慮到在P01和P02中最后給出的偽代碼只有一處不同,故如果只有兩類物品:一類物品只能取一次,另一類物品可以取無限次,那么只需在對每個(gè)物品應(yīng)用轉(zhuǎn)移方程時(shí),根據(jù)物品的類別選用順序或逆序的循環(huán)即可,復(fù)雜度是O(VN)。偽代碼如下:
for i=1..N if 第i件物品是01背包 for v=V..0 f[v]=max{f[v],f[v-c[i]]+w[i]}; else if 第i件物品是完全背包 for v=0..V f[v]=max{f[v],f[v-c[i]]+w[i]};
如果再加上有的物品最多可以取有限次,那么原則上也可以給出O(VN)的解法:遇到多重背包類型的物品用單調(diào)隊(duì)列解即可。但如果不考慮超過NOIP范圍的算法的話,用P03中將每個(gè)這類物品分成O(log n[i])個(gè)01背包的物品的方法也已經(jīng)很優(yōu)了。
小結(jié):有人說,困難的題目都是由簡單的題目疊加而來的。這句話是否公理暫且存之不論,但它在本講中已經(jīng)得到了充分的體現(xiàn)。本來01背包、完全背包、多重背包都不是什么難題,但將它們簡單地組合起來以后就得到了這樣一道一定能嚇倒不少人的題目。但只要基礎(chǔ)扎實(shí),領(lǐng)會三種基本背包問題的思想,就可以做到把困難的題目拆分成簡單的題目來解決。
P05: 二維費(fèi)用的背包問題
問題:二維費(fèi)用的背包問題是指:對于每件物品,具有兩種不同的費(fèi)用;選擇這件物品必須同時(shí)付出這兩種代價(jià);對于每種代價(jià)都有一個(gè)可付出的最大值(背包容量)。問怎樣選擇物品可以得到最大的價(jià)值。設(shè)這兩種代價(jià)分別為代價(jià)1和代價(jià)2,第i件物品所需的兩種代價(jià)分別為a[i]和b[i]。兩種代價(jià)可付出的最大值(兩種背包容量)分別為V和U。物品的價(jià)值為w[i]。
算法:費(fèi)用加了一維,只需狀態(tài)也加一維即可。設(shè)f[i][v][u]表示前i件物品付出兩種代價(jià)分別為v和u時(shí)可獲得的最大價(jià)值。狀態(tài)轉(zhuǎn)移方程就是:f [i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}。如前述方法,可以只使用二維的數(shù)組:當(dāng)每件物品只可以取一次時(shí)變量v和u采用順序的循環(huán),當(dāng)物品有如完全背包問題時(shí)采用逆序的循環(huán)。當(dāng)物品有如多重背包問題時(shí)拆分物品。
物品總個(gè)數(shù)的限制:有時(shí),“二維費(fèi)用”的條件是以這樣一種隱含的方式給出的:最多只能取M件物品。這事實(shí)上相當(dāng)于每件物品多了一種“件數(shù)”的費(fèi)用,每個(gè)物品的件數(shù)費(fèi)用均為1,可以付出的最大件數(shù)費(fèi)用為M。換句話說,設(shè)f[v][m]表示付出費(fèi)用v、最多選m件時(shí)可得到的最大價(jià)值,則根據(jù)物品的類型(01、完全、多重)用不同的方法循環(huán)更新,最后在f[0..V][0..M]范圍內(nèi)尋找答案。
另外,如果要求“恰取M件物品”,則在f[0..V][M]范圍內(nèi)尋找答案。
小結(jié):事實(shí)上,當(dāng)發(fā)現(xiàn)由熟悉的動(dòng)態(tài)規(guī)劃題目變形得來的題目時(shí),在原來的狀態(tài)中加一緯以滿足新的限制是一種比較通用的方法。希望你能從本講中初步體會到這種方法。
P06: 分組的背包問題
問題:有N件物品和一個(gè)容量為V的背包。第i件物品的費(fèi)用是c[i],價(jià)值是w[i]。這些物品被劃分為若干組,每組中的物品互相沖突,最多選一件。求解將哪些物品裝入背包可使這些物品的費(fèi)用總和不超過背包容量,且價(jià)值總和最大。
算法:這個(gè)問題變成了每組物品有若干種策略:是選擇本組的某一件,還是一件都不選。也就是說設(shè)f[k][v]表示前k組物品花費(fèi)費(fèi)用v能取得的最大權(quán)值,則有f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i屬于第k組}。
使用一維數(shù)組的偽代碼如下:
for 所有的組k for 所有的i屬于組k for v=V..0 f[v]=max{f[v],f[v-c[i]]+w[i]}
另外,顯然可以對每組中的物品應(yīng)用P02中“一個(gè)簡單有效的優(yōu)化”。
小結(jié):分組的背包問題將彼此互斥的若干物品稱為一個(gè)組,這建立了一個(gè)很好的模型。不少背包問題的變形都可以轉(zhuǎn)化為分組的背包問題(例如P07),由分組的背包問題進(jìn)一步可定義“泛化物品”的概念,十分有利于解題。
P07: 有依賴的背包問題
簡化的問題:這種背包問題的物品間存在某種“依賴”的關(guān)系。也就是說,i依賴于j,表示若選物品i,則必須選物品j。為了簡化起見,我們先設(shè)沒有某個(gè)物品既依賴于別的物品,又被別的物品所依賴;另外,沒有某件物品同時(shí)依賴多件物品。
算法:這個(gè)問題由NOIP2006金明的預(yù)算方案一題擴(kuò)展而來。遵從該題的提法,將不依賴于別的物品的物品稱為“主件”,依賴于某主件的物品稱為“附件”。由這個(gè)問題的簡化條件可知所有的物品由若干主件和依賴于每個(gè)主件的一個(gè)附件集合組成。
按照背包問題的一般思路,僅考慮一個(gè)主件和它的附件集合??墒牵捎玫牟呗苑浅6?,包括:一個(gè)也不選,僅選擇主件,選擇主件后再選擇一個(gè)附件,選擇主件后再選擇兩個(gè)附件……無法用狀態(tài)轉(zhuǎn)移方程來表示如此多的策略。(事實(shí)上,設(shè)有n個(gè)附件,則策略有2^n+1個(gè),為指數(shù)級。)
考慮到所有這些策略都是互斥的(也就是說,你只能選擇一種策略),所以一個(gè)主件和它的附件集合實(shí)際上對應(yīng)于P06中的一個(gè)物品組,每個(gè)選擇了主件又選擇了若干個(gè)附件的策略對應(yīng)于這個(gè)物品組中的一個(gè)物品,其費(fèi)用和價(jià)值都是這個(gè)策略中的物品的值的和。但僅僅是這一步轉(zhuǎn)化并不能給出一個(gè)好的算法,因?yàn)槲锲方M中的物品還是像原問題的策略一樣多。
再考慮P06中的一句話:可以對每組中的物品應(yīng)用P02中“一個(gè)簡單有效的優(yōu)化”。這提示我們,對于一個(gè)物品組中的物品,所有費(fèi)用相同的物品只留一個(gè)價(jià)值最大的,不影響結(jié)果。所以,我們可以對主件i的“附件集合”先進(jìn)行一次01背包,得到費(fèi)用依次為0..V-c[i]所有這些值時(shí)相應(yīng)的最大價(jià)值f'[0..V-c[i]]。那么這個(gè)主件及它的附件集合相當(dāng)于V-c[i]+1個(gè)物品的物品組,其中費(fèi)用為c[i]+k的物品的價(jià)值為f'[k]+w[i]。也就是說原來指數(shù)級的策略中有很多策略都是冗余的,通過一次01背包后,將主件i轉(zhuǎn)化為 V-c[i]+1個(gè)物品的物品組,就可以直接應(yīng)用P06的算法解決問題了。
更一般的問題是:依賴關(guān)系以圖論中“森林”的形式給出(森林即多叉樹的集合),也就是說,主件的附件仍然可以具有自己的附件集合,限制只是每個(gè)物品最多只依賴于一個(gè)物品(只有一個(gè)主件)且不出現(xiàn)循環(huán)依賴。
解決這個(gè)問題仍然可以用將每個(gè)主件及其附件集合轉(zhuǎn)化為物品組的方式。唯一不同的是,由于附件可能還有附件,就不能將每個(gè)附件都看作一個(gè)一般的01 背包中的物品了。若這個(gè)附件也有附件集合,則它必定要被先轉(zhuǎn)化為物品組,然后用分組的背包問題解出主件及其附件集合所對應(yīng)的附件組中各個(gè)費(fèi)用的附件所對應(yīng)的價(jià)值。
事實(shí)上,這是一種樹形DP,其特點(diǎn)是每個(gè)父節(jié)點(diǎn)都需要對它的各個(gè)兒子的屬性進(jìn)行一次DP以求得自己的相關(guān)屬性。這已經(jīng)觸及到了“泛化物品”的思想??赐關(guān)08后,你會發(fā)現(xiàn)這個(gè)“依賴關(guān)系樹”每一個(gè)子樹都等價(jià)于一件泛化物品,求某節(jié)點(diǎn)為根的子樹對應(yīng)的泛化物品相當(dāng)于求其所有兒子的對應(yīng)的泛化物品之和。
小結(jié):物品不能既作主件又作附件,每個(gè)主件最多有兩個(gè)附件,可以發(fā)現(xiàn)一個(gè)主件和它的兩個(gè)附件等價(jià)于一個(gè)由四個(gè)物品組成的物品組,這便揭示了問題的某種本質(zhì)。
P08: 泛化物品
定義:考慮這樣一種物品,它并沒有固定的費(fèi)用和價(jià)值,而是它的價(jià)值隨著你分配給它的費(fèi)用而變化。這就是泛化物品的概念。
更嚴(yán)格的定義之。在背包容量為V的背包問題中,泛化物品是一個(gè)定義域?yàn)?..V中的整數(shù)的函數(shù)h,當(dāng)分配給它的費(fèi)用為v時(shí),能得到的價(jià)值就是h(v)。
這個(gè)定義有一點(diǎn)點(diǎn)抽象,另一種理解是一個(gè)泛化物品就是一個(gè)數(shù)組h[0..V],給它費(fèi)用v,可得到價(jià)值h[V]。
一個(gè)費(fèi)用為c價(jià)值為w的物品,如果它是01背包中的物品,那么把它看成泛化物品,它就是除了h(c)=w其它函數(shù)值都為0的一個(gè)函數(shù)。如果它是完全背包中的物品,那么它可以看成這樣一個(gè)函數(shù),僅當(dāng)v被c整除時(shí)有h(v)=v/c*w,其它函數(shù)值均為0。如果它是多重背包中重復(fù)次數(shù)最多為n的物品,那么它對應(yīng)的泛化物品的函數(shù)有h(v)=v/c*w僅當(dāng)v被c整除且v/c<=n,其它情況函數(shù)值均為0。
一個(gè)物品組可以看作一個(gè)泛化物品h。對于一個(gè)0..V中的v,若物品組中不存在費(fèi)用為v的的物品,則h(v)=0,否則h(v)為所有費(fèi)用為v的物品的最大價(jià)值。P07中每個(gè)主件及其附件集合等價(jià)于一個(gè)物品組,自然也可看作一個(gè)泛化物品。
如果面對兩個(gè)泛化物品h和l,要用給定的費(fèi)用從這兩個(gè)泛化物品中得到最大的價(jià)值,怎么求呢?事實(shí)上,對于一個(gè)給定的費(fèi)用v,只需枚舉將這個(gè)費(fèi)用如何分配給兩個(gè)泛化物品就可以了。同樣的,對于0..V的每一個(gè)整數(shù)v,可以求得費(fèi)用v分配到h和l中的最大價(jià)值f(v)。也即f(v)=max{h(k) +l(v-k)|0<=k<=v}。可以看到,f也是一個(gè)由泛化物品h和l決定的定義域?yàn)?..V的函數(shù),也就是說,f是一個(gè)由泛化物品h和 l決定的泛化物品。
由此可以定義泛化物品的和:h、l都是泛化物品,若泛化物品f滿足f(v)=max{h(k)+l(v-k)|0<=k<=v},則稱f是h與l的和,即f=h+l。這個(gè)運(yùn)算的時(shí)間復(fù)雜度是O(V^2)。
泛化物品的定義表明:在一個(gè)背包問題中,若將兩個(gè)泛化物品代以它們的和,不影響問題的答案。事實(shí)上,對于其中的物品都是泛化物品的背包問題,求它的答案的過程也就是求所有這些泛化物品之和的過程。設(shè)此和為s,則答案就是s[0..V]中的最大值。
背包問題的泛化物品
一個(gè)背包問題中,可能會給出很多條件,包括每種物品的費(fèi)用、價(jià)值等屬性,物品之間的分組、依賴等關(guān)系等。但肯定能將問題對應(yīng)于某個(gè)泛化物品。也就是說,給定了所有條件以后,就可以對每個(gè)非負(fù)整數(shù)v求得:若背包容量為v,將物品裝入背包可得到的最大價(jià)值是多少,這可以認(rèn)為是定義在非負(fù)整數(shù)集上的一件泛化物品。這個(gè)泛化物品——或者說問題所對應(yīng)的一個(gè)定義域?yàn)榉秦?fù)整數(shù)的函數(shù)——包含了關(guān)于問題本身的高度濃縮的信息。一般而言,求得這個(gè)泛化物品的一個(gè)子域(例如0..V)的值之后,就可以根據(jù)這個(gè)函數(shù)的取值得到背包問題的最終答案。
綜上所述,一般而言,求解背包問題,即求解這個(gè)問題所對應(yīng)的一個(gè)函數(shù),即該問題的泛化物品。而求解某個(gè)泛化物品的一種方法就是將它表示為若干泛化物品的和然后求之。
P09: 背包問題問法的變化
以上涉及的各種背包問題都是要求在背包容量(費(fèi)用)的限制下求可以取到的最大價(jià)值,但背包問題還有很多種靈活的問法,在這里值得提一下。但是我認(rèn)為,只要深入理解了求背包問題最大價(jià)值的方法,即使問法變化了,也是不難想出算法的。
例如,求解最多可以放多少件物品或者最多可以裝滿多少背包的空間。這都可以根據(jù)具體問題利用前面的方程求出所有狀態(tài)的值(f數(shù)組)之后得到。
還有,如果要求的是“總價(jià)值最小”“總件數(shù)最小”,只需簡單的將上面的狀態(tài)轉(zhuǎn)移方程中的max改成min即可。
下面說一些變化更大的問法。
輸出方案:一般而言,背包問題是要求一個(gè)最優(yōu)值,如果要求輸出這個(gè)最優(yōu)值的方案,可以參照一般動(dòng)態(tài)規(guī)劃問題輸出方案的方法:記錄下每個(gè)狀態(tài)的最優(yōu)值是由狀態(tài)轉(zhuǎn)移方程的哪一項(xiàng)推出來的,換句話說,記錄下它是由哪一個(gè)策略推出來的。便可根據(jù)這條策略找到上一個(gè)狀態(tài),從上一個(gè)狀態(tài)接著向前推即可。
還是以01背包為例,方程為f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}。再用一個(gè)數(shù)組g[i] [v],設(shè)g[i][v]=0表示推出f[i][v]的值時(shí)是采用了方程的前一項(xiàng)(也即f[i][v]=f[i-1][v]),g[i][v]表示采用了方程的后一項(xiàng)。注意這兩項(xiàng)分別表示了兩種策略:未選第i個(gè)物品及選了第i個(gè)物品。那么輸出方案的偽代碼可以這樣寫(設(shè)最終狀態(tài)為f[N][V]):
i=N v=V while(i>0) if(g[i][v]==0) print "未選第i項(xiàng)物品" else if(g[i][v]==1) print "選了第i項(xiàng)物品" v=v-c[i]
另外,采用方程的前一項(xiàng)或后一項(xiàng)也可以在輸出方案的過程中根據(jù)f[i][v]的值實(shí)時(shí)地求出來,也即不須紀(jì)錄g數(shù)組,將上述代碼中的g[i] [v]==0改成f[i][v]==f[i-1][v],g[i][v]==1改成f[i][v]==f[i-1][v-c[i]]+w[i]也可。
輸出字典序最小的最優(yōu)方案:這里“字典序最小”的意思是1..N號物品的選擇方案排列出來以后字典序最小。以輸出01背包最小字典序的方案為例。
一般而言,求一個(gè)字典序最小的最優(yōu)方案,只需要在轉(zhuǎn)移時(shí)注意策略。首先,子問題的定義要略改一些。我們注意到,如果存在一個(gè)選了物品1的最優(yōu)方案,那么答案一定包含物品1,原問題轉(zhuǎn)化為一個(gè)背包容量為v-c[1],物品為2..N的子問題。反之,如果答案不包含物品1,則轉(zhuǎn)化成背包容量仍為V,物品為2..N的子問題。不管答案怎樣,子問題的物品都是以i..N而非前所述的1..i的形式來定義的,所以狀態(tài)的定義和轉(zhuǎn)移方程都需要改一下。但也許更簡易的方法是先把物品逆序排列一下,以下按物品已被逆序排列來敘述。
在這種情況下,可以按照前面經(jīng)典的狀態(tài)轉(zhuǎn)移方程來求值,只是輸出方案的時(shí)候要注意:從N到1輸入時(shí),如果f[i][v]==f[i-v]及f[i][v]==f[i-1][f-c[i]]+w[i]同時(shí)成立,應(yīng)該按照后者(即選擇了物品i)來輸出方案。
求方案總數(shù):對于一個(gè)給定了背包容量、物品費(fèi)用、物品間相互關(guān)系(分組、依賴等)的背包問題,除了再給定每個(gè)物品的價(jià)值后求可得到的最大價(jià)值外,還可以得到裝滿背包或?qū)⒈嘲b至某一指定容量的方案總數(shù)。
對于這類改變問法的問題,一般只需將狀態(tài)轉(zhuǎn)移方程中的max改成sum即可。例如若每件物品均是01背包中的物品,轉(zhuǎn)移方程即為f[i][v]=sum{f[i-1][v],f[i-1][v-c[i]]+w[i]},初始條件f[0][0]=1。
事實(shí)上,這樣做可行的原因在于狀態(tài)轉(zhuǎn)移方程已經(jīng)考察了所有可能的背包組成方案。
最優(yōu)方案的總數(shù)這里的最優(yōu)方案是指物品總價(jià)值最大的方案。還是以01背包為例。
結(jié)合求最大總價(jià)值和方案總數(shù)兩個(gè)問題的思路,最優(yōu)方案的總數(shù)可以這樣求:f[i][v]意義同前述,g[i][v]表示這個(gè)子問題的最優(yōu)方案的總數(shù),則在求f[i][v]的同時(shí)求g[i][v]的偽代碼如下:
for i=1..N for v=0..V f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]} g[i][v]=0 if(f[i][v]==f[i-1][v]) inc(g[i][v],g[i-1][v] if(f[i][v]==f[i-1][v-c[i]]+w[i]) inc(g[i][v],g[i-1][v-c[i]])
如果你是第一次看到這樣的問題,請仔細(xì)體會上面的偽代碼。
小結(jié):顯然,這里不可能窮盡背包類動(dòng)態(tài)規(guī)劃問題所有的問法。甚至還存在一類將背包類動(dòng)態(tài)規(guī)劃問題與其它領(lǐng)域(例如數(shù)論、圖論)結(jié)合起來的問題,在這篇論背包問題的專文中也不會論及。但只要深刻領(lǐng)會前述所有類別的背包問題的思路和狀態(tài)轉(zhuǎn)移方程,遇到其它的變形問法,只要題目難度還屬于NOIP,應(yīng)該也不難想出算法。
C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍(lán)橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習(xí)題和答疑,點(diǎn)擊了解:
一點(diǎn)編程也不會寫的:零基礎(chǔ)C語言學(xué)練課程
解決困擾你多年的C語言疑難雜癥特性的C語言進(jìn)階課程
從零到寫出一個(gè)爬蟲的Python編程課程
只會語法寫不出代碼?手把手帶你寫100個(gè)編程真題的編程百練課程
信息學(xué)奧賽或C++選手的 必學(xué)C++課程
藍(lán)橋杯ACM、信息學(xué)奧賽的必學(xué)課程:算法競賽課入門課程
手把手講解近五年真題的藍(lán)橋杯輔導(dǎo)課程