1. 程式人生 > >揹包九講-附錄(一) USACO中的揹包問題

揹包九講-附錄(一) USACO中的揹包問題

        USACO是USA Computing Olympiad的簡稱,它組織了很多面向全球的計算機競賽活動。

        USACO Trainng是一個很適合初學者的題庫,我認為它的特色是題目質量高,循序漸進,還配有不錯的課文和題目分析。其中關於揹包問題的那篇課文 (TEXT Knapsack Problems) 也值得一看。

        另外,USACO Contest是USACO常年組織的面向全球的競賽系列,在此也推薦NOIP選手參加。

        我整理了USACO Training中涉及揹包問題的題目,應該可以作為不錯的習題。其中標加號的是我比較推薦的,標歎號的是我認為對NOIP選手比較有挑戰性的。

題目列表

  • Inflate (+) (基本01揹包)
  • Stamps (+)(!) (對初學者有一定挑戰性)
  • Money
  • Nuggets
  • Subsets
  • Rockers (+) (另一類有趣的“二維”揹包問題)
  • Milk4 (!) (很怪的揹包問題問法,較難用純DP求解)

題目簡解 

        以下文字來自我所撰的《USACO心得》一文,該文的完整版本,包括我的程式,可在

DD的USACO征程中找到

        Inflate 是加權01 揹包問題,也就是說:每種物品只有一件,只可以選擇放或者 不放;而且每種物品有對應的權值,目標是使總權值最大或最小。它最樸素的狀態 轉移方程是:f[k][i] = max{f[k-1][i] , f[k-1][i-v[k]]+w[k]}。f[k][i]表示前k 件物品花費代 價i 可以得到的最大權值。v[k]和w[k]分別是第k 件物品的花費和權值。可以看到, f[k]的求解過程就是使用第k 件物品對f[k-1]進行更新的過程。那麼事實上就不用使用 二維陣列,只需要定義f[i],然後對於每件物品k,順序地檢查f[i]與f[i-v[k]]+w[k]的大 小,如果後者更大,就對前者進行更新。這是揹包問題中典型的優化方法。

        題目stamps 中,每種物品的使用量沒有直接限制,但使用物品的總量有限制。 求第一個不能用這有限個物品組成的揹包的大小。(可以這樣等價地認為)設f[k][i] 表示前k 件物品組成大小為i 的揹包, 最少需要物品的數量。則f[k][i]= min{f[k-1][i],f[k-1][i-j*s[k]]+j},其中j 是選擇使用第k 件物品的數目,這個方程運用時 可以用和上面一樣的方法處理成一維的。求解時先設定一個粗糙的迴圈上限,即最 大的物品乘最多物品數。

        Money 是多重揹包問題。也就是每個物品可以使用無限多次。要求解的是構成 一種揹包的不同方案總數。基本上就是把一般的多重揹包的方程中的min 改成sum 就行了。

        Nuggets 的模型也是多重揹包。要求求解所給的物品不能恰好放入的揹包大小 的最大值(可能不存在)。只需要根據“若i、j 互質,則關於x、y 的不定方程ix+yj=n 必有正整數解,其中n>ij”這一定理得出一個迴圈的上限。 Subsets 子集和問題相當於物品大小是前N 個自然數時求大小為N(N+1)/4 的 01 揹包的方案數。

        Rockers 可以利用求解揹包問題的思想設計解法。我的狀態轉移方程如下: f[i][j][t]=max{f[i][j][t-1] , f[i-1][j][t] , f[i-1][j][t-time[i]]+1 , f[i-1][j-1][T]+(t>=time[i])}。其中 f[i][j][t]表示前i 首歌用j 張完整的盤和一張錄了t 分鐘的盤可以放入的最多歌數,T 是 一張光碟的最大容量,t>=time[i]是一個bool 值轉換成int 取值為0 或1。但我後來發 現我當時設計的狀態和方程效率有點低,如果換成這樣:f[i][j]=(a,b)表示前i 首歌中 選了j 首需要用到a 張完整的光碟以及一張錄了b 分鐘的光碟,會將時空複雜度都大 大降低。這種將狀態的值設為二維的方法值得注意。

        Milk4 是這些類揹包問題中難度最大的一道了。很多人無法做到將它用純DP 方 法求解,而是用迭代加深搜尋列舉使用的桶,將其轉換成多重揹包問題再DP。由於 USACO 的資料弱,迭代加深的深度很小,這樣也可以AC,但我們還是可以用純DP 方法將它完美解決的。設f[k]為稱量出k 單位牛奶需要的最少的桶數。那麼可以用類 似多重揹包的方法對f 陣列反覆更新以求得最小值。然而困難在於如何輸出字典序最 小的方案。我們可以對每個i 記錄pre_f[i]和pre_v[i]。表示得到i 單位牛奶的過程是 用pre_f[i]單位牛奶加上若干個編號為pre_v[i]的桶的牛奶。這樣就可以一步步求得得 到i 單位牛奶的完整方案。為了使方案的字典序最小,我們在每次找到一個耗費桶數 相同的方案時對已儲存的方案和新方案進行比較再決定是否更新方案。為了使這種 比較快捷,在使用各種大小的桶對f 陣列進行更新時先大後小地進行。

        USACO 的官方題解正是這一思路。如果認為以上文字比較難理解可以閱讀官方程式或我的程式。