• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            雁過無痕

              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::

            多重背包O(N*V)算法詳解(使用單調隊列)

             

            多重背包問題:

            N種物品和容量為V的背包,若第i種物品,容量為v[i],價值為w[i],共有n[i]件。怎樣裝才能使背包內的物品總價值最大?

             

            網上關于“多重背包”的資料倒是不少,但是關于怎么實現O(N*V)算法的資料,真得好少呀,關于“單調隊列”那部分算法,又沒說明得很清楚,看了幾遍沒看懂原理,只好自己動腦去想怎么實現O(N*V)算法。

             

            若用F[i][j]表示對容量為j的背包,處理完前i種物品后,背包內物品可達到的最大總價值,并記m[i] = min(n[i], j / v[i])。放入背包的第i種物品的數目可以是:012……,可得:

            F[i][j] = max { F[i - 1] [j – k * v[i] ] + k * w[i] }  (0 <= k <= m[i])      

               

            如何在O(1)時間內求出F[i][j]呢?

            先看一個例子:取m[i] = 2, v[i] = v, w[i] = w, V > 9 * v

            并假設 f(j) = F[i - 1][j],觀察公式右邊要求最大值的幾項:

            j = 6*v:   f(6*v)f(5*v)+wf(4*v)+2*w 這三個中的最大值

            j = 5*v:   f(5*v)f(4*v)+wf(3*v)+2*w 這三個中的最大值

            j = 4*v:   f(4*v)f(3*v)+wf(2*v)+2*w 這三個中的最大值

            顯然,公式㈠右邊求最大值的幾項隨j值改變而改變,但如果將j = 6*v時,每項減去6*wj=5*v時,每項減去5*wj=4*v時,每項減去4*w,就得到:

            j = 6*v:   f(6*v)-6*wf(5*v)-5*wf(4*v)-4*w 這三個中的最大值

            j = 5*v:   f(5*v)-5*wf(4*v)-4*wf(3*v)-3*w 這三個中的最大值

            j = 4*v:   f(4*v)-4*wf(3*v)-3*wf(2*v)-2*w 這三個中的最大值

            很明顯,要求最大值的那些項,有很多重復。

             

            根據這個思路,可以對原來的公式進行如下調整:

            假設d = v[i]a = j / db = j % d,即 j = a * d + b,代入公式㈠,并用k替換a - k得:

            F[i][j] = max { F[i - 1] [b + k * d] - k * w[i] } + a * w[i]   (a – m[i] <= k <= a)   

             

            F[i - 1][y] y= b  b+d  b+2d  b+3d  b+4d  b+5d  b+6d    j

            F[i][j]就是求j的前面m[i] + 1個數對應的F[i - 1] [b + k * d] - k * w[i]的最大值,加上a * w[i],如果將F[i][j]前面所有的F[i - 1][b + k * d] – k * w放入到一個隊列,那么,F[i][j]就是求這個隊列最大長度為m[i] + 1時,隊列中元素的最大值,加上a * w[i]因而原問題可以轉化為:O(1)時間內求一個隊列的最大值

            該問題可以這樣解決:

            ① 用另一個隊列B記錄指定隊列的最大值(或者記錄最大值的地址),并通過下面兩個操作保證隊列B的第一個元素(或其所指向的元素)一定是指定隊列的當前最大值。

            ② 當指定隊列有元素M進入時,刪除隊列B中的比M小的(或隊列B中所指向的元素小等于M的)所有元素,并將元素M(或其地址)存入隊列B

            ③ 當指定隊列有元素M離開時,隊列B中的第一個元素若與M相等(或隊列B第一個元素的地址與M相等),則隊列B的第一個元素也離隊。

            經過上述處理,可以保證隊列B中的第一個元素(或其指向的元素)一定是所指定隊列所有元素的最大值。顯然隊列B的元素(或其所指向的元素)是單調遞減的,這應該就是《背包九講》中的提到的“單調隊列”吧,初看的時候被這個概念弄得稀里糊涂,網上的資料提到“維護隊列的最大值”,剛開始還以為是維護這個單調隊列的最大值,對其采用的算法,越看越糊涂。其實,只要明白用一個“輔助隊列”,求另一個隊列的最值,那么具體的算法,和該“輔助隊列”的性質(單調變化),都很容易推導出來。

            在多重背包問題中,所有要進入隊列的元素個數的上限值是已知的,可以直接用一個大數組模擬隊列。

            “多重背包”通用模板



            多重背包特例:物品價值和體積相等(w = v)

            由于w = v,上面的代碼可進行如下修改:

            入隊的元素: tt = f[k] - (k / v) * w = f[k] - (k - j) = f[k] - k + j

                返回的最大值:*qb + (k / v) * w =  *qb + k - j

            由于j是定值,可調整入隊的元素為: f[k] - k,最大值為 *qb + k

             

            但這種做法相當低效。實際上,這相當于一個“覆蓋”問題:在放入前i個物品后,體積為j的背包,只存在兩種狀態:是否能剛好裝滿,也就是,是否能被覆蓋。因而只要記錄下該狀態就可以了,前面的分析進行相應的調整:

            F[i - 1][y] y= b  b+d  b+2d  b+3d  b+4d  b+5d  b+6d    j

            F[i][j]就是求j的前面m[i] + 1個數對應的F[i - 1] [b + k * d](其值為01)的最大值,即j前面的m[i] + 101數據中是否存在1,這又可以簡化為判斷它們的和是否不等于0


            pack-01

             

            另外,可以倒著讀數據,這樣就不需要額外使用一個數組存放臨時數據:


            pack-02

             

            前面的代碼,都在循環中對隊列的元素個數進行判斷,這可以通過下面的方法避免,將循環拆分成兩部分:一部分都有入隊和出隊操作、另一部分只有入隊(或出隊)操作。

             

            對該特例,還有一種O(N * V)解法:用一個數組記錄當前物品已經使用數,關鍵代碼:

            if (! f[i] && f[i - v] && count[i - v] < n)

            f[i] = true, count[i] = count[i - v] + 1;

            每計算一類物品,count數組都要初始化一次,比較費時,可以再用一個數組記錄上一次處理的物品編號,通過判斷上一次放入那一類的物品編號與當前這類物品編號是否一致(不一致時,相當于count[i]值為0的情況),從而避免對count數組的初始化操作。還可以將初始化count數組和后面的循環整合在一起。


            pack-1

            pack-2

            pack-4


            POJ 1742
            :有若干不同面值的紙幣,問能組合出1m中的幾種面值?


            poj 1742

            用自己隨機生成的數據測試了下,上面提到的幾種方法,所用時間都是7秒多點,有排序的比沒排序的稍微快點。但在POJ上提交的結果,不同代碼的耗時相差挺大,快的在1秒左右,慢的接近1.5秒。

             


            還有一種特例:

            給定面值為125的紙幣若干個,問其所不能支付的最低價格(假設為自然數)。

            這可以用多重背包(或母函數)來解決,但實際上是有O(1)解法的。

            posted on 2010-09-01 23:25 flyinghearts 閱讀(6518) 評論(5)  編輯 收藏 引用 所屬分類: 算法

            評論

            # re: 多重背包O(N*V)算法詳解(使用單調隊列) 2010-10-02 11:56 040374
            能把用單調隊列解poj1742的思想寫得更詳細點嗎?thx  回復  更多評論
              

            # re: 多重背包O(N*V)算法詳解(使用單調隊列) 2010-10-04 18:44 flyinghearts
            @040374
            在V=W這種特例中,單調隊列是可以不用的,只要采用隊列思想就可以了。
            對每個值只記錄兩種狀態,要判斷的是:(間隔為v)的連續n個狀態中是否為1,這等價于判斷它們的和是否不為0,
            如果從前往后讀取這些狀態時,就需要一個臨時數組,不然新狀態值會覆蓋舊狀態值。
            但是,我們可以從后往前讀取這些狀態值,這樣就不需要臨時數組了。

            對V=W這種特例,“狀態+計數”(這也是很多網友采取的方法)這種方法,效率會更高,
            一般來說,在判斷狀態中后期,狀態為真的占大多數,這樣采用計數的方法就只要訪問某個狀態值一次,
            而上面的方法,則要兩次(進出隊各一次)。

              回復  更多評論
              

            # re: 多重背包O(N*V)算法詳解(使用單調隊列)[未登錄] 2012-11-16 11:11 kai
            前面部分講的很清楚,謝謝

            多重背包特例:物品價值和體積相等(w = v)

            tt = f[k] - (k / v) * w = f[k] - (k - j) = f[k] - k + j

            好像沒說清楚

            改成

            因為
            F[i - 1] [b + k * d] - k * w[i]

            d = w



            b + k * d = k'
            ->
            k = (k' - b)/d
            ->
            tt = f[k'] - ( (k' - b)/ d) * w = f[k'] - (k'-b) = f[k'] - k' + b

            在循環
            for (int k = j, i = 0; k <= V; k += v, ++i)

            里面

            b其實是j, k' 其實是k

            f[k'] - k' + b = f[k] - k + j
              回復  更多評論
              

            # re: 多重背包O(N*V)算法詳解(使用單調隊列)[未登錄] 2012-11-18 06:01 kai
            > 另外,可以倒著讀數據,這樣就不需要額外使用一個數組存放臨時數據:

            正向著來為什么不可以?好像也說的通吧  回復  更多評論
              

            # re: 多重背包O(N*V)算法詳解(使用單調隊列) 2014-10-12 09:59 天天好贏錢
            有入口簡而言之是指在發布站點的相關頻道可以看到軟文標題,點擊標題進入www.fuc688.com軟文內容頁面;而無入口則是指只提供文章內容的網址鏈接,在頻道頁面找不到軟文標題。有入口的軟文有助于線上網友的瀏覽,提高點擊率,可更好地擴大宣傳面;無入口的軟文適合做成書面的形式,用word或ppt等做成效果圖表,適用于線下的講座、匯報等。我們發布的軟文發布絕大部分都  回復  更多評論
              

            国产一区二区精品久久凹凸| 久久亚洲AV无码精品色午夜| 亚洲国产日韩欧美久久| 亚洲AV无码久久| 色偷偷88888欧美精品久久久| 国内精品九九久久久精品| 久久亚洲2019中文字幕| 91精品国产9l久久久久| 久久99精品国产麻豆宅宅| 久久久久久久波多野结衣高潮| 日韩精品久久无码中文字幕| 日日狠狠久久偷偷色综合免费| 久久精品亚洲中文字幕无码麻豆| 青草影院天堂男人久久| 99国内精品久久久久久久| 久久久精品2019免费观看| 久久99九九国产免费看小说| 久久久久亚洲AV无码专区首JN | 国产精品伊人久久伊人电影| 中文字幕久久精品无码| 久久久久久精品免费看SSS| 久久久久99这里有精品10| 国产免费福利体检区久久 | 一本一本久久A久久综合精品 | 久久人人爽人人爽人人片AV东京热| 久久人爽人人爽人人片AV| 免费一级做a爰片久久毛片潮| 色偷偷88888欧美精品久久久 | 久久综合九色综合精品| 99re这里只有精品热久久| 一级做a爰片久久毛片毛片| 国产精品无码久久久久久| A狠狠久久蜜臀婷色中文网| 亚洲综合久久综合激情久久 | 久久精品国产亚洲网站| 99久久国产主播综合精品| 久久婷婷色综合一区二区| 国产成人综合久久久久久| 久久婷婷五月综合色奶水99啪| 久久久久久精品免费免费自慰| 国产福利电影一区二区三区久久久久成人精品综合 |