背包问题-C++
第一講 01背包
題目
給定物品個(gè)數(shù)n,背包容量v,每個(gè)物品都有一個(gè)體積c和價(jià)值w,要求向背包中裝物品使得總價(jià)值最高.
基本思路
狀態(tài)表示:f(i,j)表示前i個(gè)物品試圖裝入一個(gè)容量為j的背包的最大價(jià)值.?
邊界情況:f(0,j)=0.?
狀態(tài)轉(zhuǎn)移:f(i,j)=max(f(i-1,j),f(i,j-save[i])+value[i]). 即裝或不裝第i個(gè)物品?
時(shí)間復(fù)雜度O(VN) 空間復(fù)雜度O(VN)
例1 hdu2602 01背包裸題?
兩個(gè)wa點(diǎn),第一個(gè)是多組數(shù)據(jù)沒有注意到,第二個(gè)是存在體積為0的情況,所以j需要從0枚舉起.
?
? ? ? memset(dp,0,sizeof(dp));for(int i=1;i<=n;i++) scanf("%d",&value[i]);for(int i=1;i<=n;i++) scanf("%d",&volume[i]);for(int i=1;i<=n;i++)for(int j=0;j<=v;j++)if(j<volume[i])dp[i][j]=dp[i-1][j];elsedp[i][j]=max(dp[i-1][j],dp[i-1][j-volume[i]]+value[i]);printf("%d\n",dp[n][v] );
優(yōu)化空間復(fù)雜度
注意到每個(gè)狀態(tài)f(i,j)只與之前的狀態(tài)f(i-1,j)和f(i-1,j-save[i])有關(guān).實(shí)際上開一個(gè)二維數(shù)組是不必要的,完全可以開一個(gè)一維數(shù)組下標(biāo)為j,然后循環(huán)遞推n次.?
即f(j)=max(f(j),f(j-save[i])+value[i]),但是正向循環(huán)是錯(cuò)誤的,因?yàn)橹暗膉-save[i]已經(jīng)被新狀態(tài)所覆蓋,正確的做法只要反轉(zhuǎn)一下順序,逆向循環(huán)就好.
? ? ?
? int n=read(),v=read();for(int i=1;i<=n;i++)value[i]=read();for(int i=1;i<=n;i++)volume[i]=read();memset(dp,0,sizeof(dp));for(int i=1;i<=n;i++)for(int j=v;j>=volume[i];j--)dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);printf("%d\n",dp[v] );
注意此時(shí)這個(gè)數(shù)組的意義是:容量為j的背包最多能裝多少價(jià)值的物品.?
遍歷i個(gè)物品后,得到結(jié)果.
初始化的細(xì)節(jié)問題(滿包)
01背包滿包問題:給定物品個(gè)數(shù)n,背包容量v,每個(gè)物品都有一個(gè)體積c和價(jià)值w,請問恰好裝滿背包時(shí)最大總價(jià)值是多少.?
狀態(tài)表示,轉(zhuǎn)移方程同理:f(j)=max(f(j),f(j-save[i])+value[i]))?
但是初始條件:f(j)=-∞,f(0)=0.意義是0件物品時(shí)不可能裝到除0之外的容量,-∞表示無解.?
遞推結(jié)束后會(huì)有很多狀態(tài)都是無解,表示不能達(dá)到.
例2 luogu P1164 小A點(diǎn)菜 01背包滿包求方案數(shù)?
狀態(tài)表示:f(j)表示容量為j的背包裝滿有幾種方案.?
初始條件:f(j)=0,f(0)=1?
狀態(tài)轉(zhuǎn)移:f(j)=f(j)+f(j-save[i])
int n=read(),m=read();for(int i=1;i<=n;i++)save[i]=read();dp[0]=1;for(int i=1;i<=n;i++)for(int j=m;j>=0;j--)dp[j]=dp[j]+dp[j-save[i]];printf("%d\n",dp[m] );
一個(gè)常數(shù)優(yōu)化
01背包二重循環(huán)的下限可以從Ci優(yōu)化成max(V ? Σ(i到n)C, Ci)
在這之前我有一個(gè)疑惑,為什么下限會(huì)是ci,這在未優(yōu)化空間復(fù)雜度時(shí)是不成立的.?
但是優(yōu)化空間復(fù)雜度后顯然成立,因?yàn)椤笔裁炊疾蛔觥本褪莇p[j]=dp[j].?
所以轉(zhuǎn)移方程就是
下面考慮原文中的優(yōu)化,這個(gè)數(shù)值是僅與i有關(guān)的.?
看了網(wǎng)上大佬的思路,意思是某些算出來的背包實(shí)際上對最終結(jié)果是沒有幫助的.?
最多有幫助的就是總體積減去之后每個(gè)物品的體積之和.?
比如計(jì)算最后一件物品時(shí),V-C(n-1)以下容量的背包是不可能轉(zhuǎn)移到結(jié)果的.?
同理,計(jì)算倒數(shù)第二件物品時(shí),V-C(n-1)-C(n-2)以下的背包也是不可能轉(zhuǎn)移到最終結(jié)果的.
? ? ?
? int n=read(),v=read(),lim=v;for(int i=1;i<=n;++i) value[i]=read();for(int i=1;i<=n;++i) volume[i]=read(),lim-=volume[i];memset(dp,0,sizeof(dp));for(int i=1;i<=n;++i){int th=max(lim,volume[i]);for(int j=v;j>=th;--j)dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);lim+=volume[i];}printf("%d\n",dp[v] );第二講 完全背包問題
?
問題
完全背包問題:給定一個(gè)容量為v的背包和n種物品,每種物品有無限個(gè)且有體積Ci和價(jià)值Wi.問最多能向背包中裝多少價(jià)值的物品?
naive approch
狀態(tài)表示:F(i,j)仍表示對于前i種物品,背包容量為j時(shí)最多價(jià)值為多少??
初始情況:F(0,j)=0?
狀態(tài)轉(zhuǎn)移:f(i,j)=max{f(i-1,j-kCi)+kWi} (0<=k<=j/Ci)?
復(fù)雜度O(V∑V/Ci)
優(yōu)化
優(yōu)化1:對于Ci<=Cj,Wi>=Wj的情況,扔掉j 需要O(N^2)進(jìn)行優(yōu)化?
優(yōu)化2:基數(shù)排序,扔掉Ci>V的i,對0到V每個(gè)容量,選擇Wi最大的物品. 需要O(V+N)
轉(zhuǎn)化
將完全背包轉(zhuǎn)化為01背包.?
第一種轉(zhuǎn)化,每種物品對應(yīng)為V/Ci個(gè)物品?
第二種轉(zhuǎn)化,將第i種物品拆分成Ci*2^k,Wi*2^k的若干件物品,滿足Ci*2^k<=V即可?
每種物品對應(yīng)log(V/Ci)個(gè)物品,優(yōu)化很大.
O(VN)算法
for(int i=1;i<=n;i++) for(int j=volume[i];j<=v;j++)dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);這個(gè)算法與01背包空間優(yōu)化后的算法,只有j的遍歷順序相反.?
因?yàn)?1背包倒序遍歷就是為了避免一件物品被計(jì)算多次.?
兩層循環(huán)可以交換順序,某些情況下帶來常數(shù)優(yōu)化???
第三講 多重背包
?
題目
給n種物品和一個(gè)容量為v的背包,每種物品最多有num[i]件可用,每個(gè)物品都有一個(gè)體積volume[i]和價(jià)值value[i],求背包最多能裝多少價(jià)值的物品?
基本算法
dp[j]表示容量為j的背包最多能裝多少價(jià)值的物品.?
dp[j]=max{dp[j-k*volume[i]]+k*value[i]} 0<=k<=num[i]?
復(fù)雜度O(VNnum[i])
如果轉(zhuǎn)換為01背包,相當(dāng)于物品數(shù)為∑num[i]的01背包問題,復(fù)雜度O(V∑num[i])
二進(jìn)制優(yōu)化
將Mi件體積為Ci,價(jià)值為Wi的物品,可以轉(zhuǎn)換為ceil(logMi)件物品:?
每件物品的體積和價(jià)值都乘以一個(gè)系數(shù),系數(shù)依次為1,2,4,8,…..,2^(k-1),Mi-(2^k-1).?
其中k滿足是Mi-(2^k-1)>0的最大整數(shù)
正確性:?
注意到做01背包時(shí)會(huì)給出每件物品取或不取的最優(yōu)情況,也即如果直接分成Mi件物品,跑完之后總會(huì)給出最優(yōu)取幾件的情況.?
而由分成的這些物品和0(不取),可以組合成0到Mi之間的任何一個(gè)數(shù)字.按01背包跑完后,同樣會(huì)給出一摸一樣的最優(yōu)取幾件的情況.?
兩者是完全等效的.
復(fù)雜度?
O(V∑log(Mi))
實(shí)現(xiàn)?
對于物品num[i],volume[i],value[i]?
如果num[i]*volume[i]>=V 直接按照完全背包來做這個(gè)物品即可?
令k=1;?
當(dāng)k<num[i],時(shí)?
—對物品k*volume[i],k*value[i]做01背包?
—num[i]-=k;?
—k<<=1?
最后,對num[i]*volue[i],num[i]*value[i]做01背包
O(VN)的解法
對于基礎(chǔ)的狀態(tài)轉(zhuǎn)移,使用單調(diào)隊(duì)列來優(yōu)化,達(dá)到O(VN)的復(fù)雜性?
在優(yōu)化dp的時(shí)候再去學(xué)習(xí)
例題
51nod 1086 背包問題V2 多重背包裸題?
完全如上文所說,有幾個(gè)需要注意的點(diǎn):?
1.要非常熟練完全背包和01背包的基礎(chǔ)代碼寫法.?
2.關(guān)于k的循環(huán)選取應(yīng)該有更好的方式
以上是略微改過的代碼,用了一個(gè)完全背包兩個(gè)01背包.?
有一個(gè)疑惑點(diǎn):如果直接將volu和value左移位,會(huì)謎之wa,保存這個(gè)問題,先略.
第四講-混合三種背包問題
問題
如果三種背包問題混合起來,即有的物品能選一次,有的物品能選多次,有的物品能選無限次,如何求解?
解法
那當(dāng)然是分類討論if else.?
其實(shí)在完全背包的解法中就有這樣的感覺.對于總體積超過背包體積的,按完全背包算.
實(shí)現(xiàn)?
01背包實(shí)際上都按多重背包解即可,復(fù)雜度不會(huì)提高.而對于完全物品,設(shè)置它的數(shù)量為v/volume[i]*10,保證不會(huì)爆范圍就行,這樣走多重背包的時(shí)候就會(huì)直接走第一個(gè)if.
其他
原文中特別注重培養(yǎng)抽象出模型的能力,實(shí)際上這個(gè)能力也非常重要.在寫多重背包時(shí),調(diào)用01背包和完全背包就使用了這樣的能力.
第五講-二維費(fèi)用的背包問題
問題
給一個(gè)容量為V的背包,你的負(fù)重最大只有W,然后有n種物品,每種都有若干個(gè)(0個(gè),無限,多個(gè)),體積為volume[i],重量為weight[i],價(jià)值為value[i].問最多能裝多少價(jià)值的物品,在不超過體積及負(fù)重的情況下?
解法
狀態(tài)加一維?
用dp[j][k]表示容量為j負(fù)重為k時(shí)的最大價(jià)值.?
則dp[j][k]=dp[j-volume[i]][k-weight[i]]+value[i].?
根據(jù)物品種類的不同,選擇01背包,完全背包,多重背包的轉(zhuǎn)移方式.
其他
常見的問題是個(gè)數(shù)限制,這個(gè)時(shí)候?qū)€(gè)數(shù)變成第二維是個(gè)不錯(cuò)的選擇.?
除此之外,原文中說二維背包實(shí)際上類似于復(fù)整數(shù)域的一維背包,我的理解是兩個(gè)維度是完全正交的,完全不相關(guān)地添加.而且用這樣地方法可以實(shí)現(xiàn)更高維地背包.
例題 vijos 1334 NASA的食物計(jì)劃
二維費(fèi)用01背包,模板題?
注意01背包和完全背包的遍歷順序不同,不要寫錯(cuò)了
第六講-分組的背包問題
題目
有n件物品可以被放入一個(gè)容量為v的背包中,每件物品體積為volume[i],價(jià)值為value[i].此外,這些物品被分成p組,每組中的物品最多只能選一件,求背包中最多可以裝多少價(jià)值的物品.
分析
對于每組物品,可以選擇這組物品中某一件,或者這組中一件也不選.?
是不是感覺這個(gè)東西有點(diǎn)像,01背包??
如果用dp(k,v)表示前k組物品最多在容量為v的背包中裝多少價(jià)值,那么顯然?
dp(k,v)=max(dp(k-1,v),dp(k-1,v-volume[i])+value[i]),其中i遍歷第k個(gè)物品集合.
實(shí)現(xiàn)
?
for(int k=1;k<=p;k++)? for(int j=v;j>=0;j–) //此處遍歷順序與物品種類有關(guān)? for(int i:part[k])? dp[j]=max(dp[j],dp[j-volume[i]]+value[i]).?
注意遍歷方式一定是kji,如果是kij的話就無法保證每組只選一個(gè)了.?
先j再i保證了每組內(nèi),每個(gè)體積只會(huì)被一個(gè)最優(yōu)的物品訪問到.
優(yōu)化
這里可用第二講完全背包中的對應(yīng)優(yōu)化?
1.n^2遍歷,將體積大價(jià)值小的物品去掉.?
2.計(jì)數(shù)排序,去掉體積大于v的物品,相同體積的物品只取價(jià)值最大的.(限制物品個(gè)數(shù)大于體積的情況?)
其他
一點(diǎn)自己的看法?
以上得思考都是基于01背包來做的,那么其他呢??
如果每組可以取無限次,每次取的物品可以不一樣:分組就沒有意義了,對所有物品完全背包即可.?
如果每組可以取無限次,每次取的物品必須相同: 這是什么問題?想不出來,先略.
?
第七講-有依賴的背包問題
有依賴的背包問題指的是如果物品j依賴于物品i, 要想選擇物品j必須先選擇了物品i.?
一個(gè)簡化的問題是:一件物品只會(huì)依賴最多一件物品,且附屬品不會(huì)被依賴.
思考
首先想一下我的dp訓(xùn)練第19題黃金礦工.那道題里實(shí)際上一條過原點(diǎn)的直線上的金塊會(huì)按順序依賴.當(dāng)時(shí)的解法是將一條線上的所有物品看成一組,然后將組中前方物品的值加在后方物品上.這種方法在解決鏈狀依賴時(shí)非常有效.
如果不是鏈狀依賴呢?比如物品2和3都依賴于物品1,這時(shí)總的情況實(shí)際上有5種:都不選,只選1,選12,選13,選123.物品有多件時(shí)情況數(shù)會(huì)成指數(shù)增長,顯然枚舉是不可行的.
簡化后的問題
背包九講原文中給出的方法是處理這一組物品,對每個(gè)費(fèi)用只保留價(jià)值最大的情況.相當(dāng)于做成了一個(gè)泛化物品(泛化物品是指某種物品給定一個(gè)費(fèi)用就會(huì)返回一個(gè)價(jià)值).然后使用第六講分組背包中的算法就可以解決.
關(guān)鍵的問題是怎么處理?此時(shí)一組物品中有1個(gè)主件和若干個(gè)附件,在選擇主件后,每個(gè)附件都是可選可不選的,現(xiàn)在需要得到每個(gè)特定費(fèi)用的最大價(jià)值,怎么做?
答案是,01背包. 對所有附件做一次01背包之后,再加上主件的花費(fèi)和價(jià)值,就得到了這個(gè)泛化物品的所有屬性.
算法
只空談算法而不寫程序是ACMer大忌!我怎么把這個(gè)忘了!?
一個(gè)標(biāo)準(zhǔn)的解法是對于每個(gè)主件從dp數(shù)組copy一個(gè)tmp數(shù)組,然后讓所有這個(gè)主件的附件對tmp數(shù)組做01背包.做完之后對每個(gè)值加上主件的體積和價(jià)值,求一個(gè)max.
對每個(gè)附件做完01背包之后,tmp表示前i-1組物品和這些附件在每個(gè)體積時(shí)的最優(yōu)情況.但是,如果需要tmp里的某個(gè)值,得先經(jīng)過主件之后才能轉(zhuǎn)移到dp中.?
從dp和tmp得到dp的過程實(shí)際上也是一個(gè)01背包,只是通常的01背包是針對數(shù)組本身選或不選,而本處則是針對兩個(gè)數(shù)組誰可以轉(zhuǎn)移到新的dp做出的選擇.?
按最優(yōu)狀態(tài)分析一下,新的dp[j]只可能會(huì)由兩個(gè)狀態(tài)轉(zhuǎn)移而來,一個(gè)是這一組物品任意一個(gè)都不選,即舊dp[j],另一個(gè)則是選了主件和某些附件,則由tmp轉(zhuǎn)移.?
這里還是很繞,多回來看一看.
更復(fù)雜的問題
復(fù)雜問題是指,主件和附件之間的依賴關(guān)變成了一棵樹,所有依賴的集合是一個(gè)森林.這里假設(shè)每個(gè)物品只能依賴一件物品且不會(huì)循環(huán)依賴.?
解法是,從葉子到根依次進(jìn)行上述的01背包過程,就得到了這整棵樹的泛化模型,森林中所有樹的泛化模型都得到后,使用分組背包的算法即可解決問題.
第八講-泛化物品
泛化物品準(zhǔn)確來說,不是一類題目,而是一種思想.泛化物品的定義是
考慮這樣一種物品,它并沒有固定的費(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]。
當(dāng)我看到這里的時(shí)候,我真正覺得這個(gè)概念的提出者和背包九講的作者崔添翼是一個(gè)了不起的人物.他用這樣的概念,簡單準(zhǔn)確的描述了到底什么是背包,背包問題到底在干什么.
一個(gè)01背包中的物品(體積ci,價(jià)值wi),它的泛化物品模型是h(ci)=wi,h(其他)=0.?
一個(gè)完全背包中的物品,它的模型是h(ci*k)=wi*k,其中k為正整數(shù)且ci*k<=V, h(其他)=0.?
一個(gè)多重背包中的物品,則是h(ci*k)=wi*k,其中k<=ni且ci*k<=V, h(其他)=0.?
一個(gè)互斥的物品組,h(ci)=wi,i取遍組中物品的編號(hào),ci相同時(shí)wi取最小值,h(其他)=0
泛化物品的和
如果給定兩個(gè)泛化物品a和b,現(xiàn)在有體積v來裝這兩種物品,要求獲得最大價(jià)值.怎么做?
上文中有提到,將泛化物品直接使用分組背包的方法可以求解,注意泛化后每一組中互斥物品個(gè)數(shù)實(shí)際上都是v了,復(fù)雜度O(n*v*v),其中n為泛化物品個(gè)數(shù).
如果僅僅有兩個(gè)物品呢,設(shè)用體積j來裝這兩種物品的最大價(jià)值為dp[j]?
則dp[j]=max{a(k)+b(v-k)},k取遍0到j(luò),答案就是dp[v]?
新合成的dp數(shù)組,實(shí)際上,也是一個(gè)泛化物品.?
由泛化物品的性質(zhì)可知,如果將兩個(gè)泛化物品這樣合成一個(gè)新的物品,新的物品在問題種完全可以取代原有的兩個(gè)物品.
背包與泛化物品
背包問題中都會(huì)有的dp數(shù)組,處理完之后的值實(shí)際上也是一個(gè)泛化物品,是所有原有物品的和.?
所以,求解背包實(shí)際上就是求得各個(gè)體積時(shí)的最優(yōu)價(jià)值,得到最終的泛化物品.
帶著泛化物品的思想,可以再去回頭看看第六和第七講.
?
第九講-背包問題問法的變化
?
背包問題的一些基礎(chǔ)問法
給定背包的體積限制,求最大價(jià)值?
給定背包的體積限制,求最小價(jià)值 (max改為min)?
給定背包的體積限制,求最大件數(shù) (價(jià)值為1)(或貪心)?
給定背包的體積限制,求最小件數(shù) (貪心)?
給定背包的體積限制,求最多裝滿多少空間(判斷每個(gè)體積是否可達(dá))
輸出方案
去掉空間優(yōu)化,變成二維數(shù)組?
記錄每個(gè)最優(yōu)的子問題是由max的哪項(xiàng)推過來的,倒推回去即可得到是否選擇某件物品?
不用顯示記錄,倒推時(shí)比較兩項(xiàng)即可.
輸出字典序最小的方案
由于前方物品比后方物品重要,最好將子問題從前i件物品改成后i件物品.?
每次倒推時(shí),如果dp[i-1][j]與dp[i-1][j-volu]+valu并列,選擇后者.?
也可以不改狀態(tài)表示,而是將所有物品做一個(gè)轉(zhuǎn)換x=n+1-x?
輸出方案時(shí)轉(zhuǎn)換回來即可
對某個(gè)體積求方案數(shù)
轉(zhuǎn)移方程改為sum?
初始條件改為dp[0][0]=1
最優(yōu)方案總數(shù)
多增加一個(gè)數(shù)組mem[i][j]表示dp[i][j]最優(yōu)時(shí)方案數(shù)有幾種?
初始條件mem[0][0]=1?
狀態(tài)轉(zhuǎn)移(在dp轉(zhuǎn)移后):if(dp[i][j]==dp[i-1][j]) mem[i][j]+=dp[i-1][j]?
if(dp[i][j]==dp[i-1][j-volu]+valu) mem[i][j]+=mem[i-1][j-volu]
求次優(yōu)解或第k優(yōu)解
每個(gè)dp狀態(tài)實(shí)際是一個(gè)有序數(shù)組,代表前k優(yōu)解?
轉(zhuǎn)移時(shí)將dp[i-1][j]與dp[i-1][j-volu]+val進(jìn)行一個(gè)merge操作,即可得到dp[i][j]?
復(fù)雜度O(NVK)
一個(gè)正確的狀態(tài)轉(zhuǎn)移方程實(shí)際遍歷了所有的解法,只不過忽略了很多非最優(yōu)解
與50位技術(shù)專家面對面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
- 上一篇: Charm Bracelet
- 下一篇: Bookshelf 2