青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

ACM___________________________

______________白白の屋
posts - 182, comments - 102, trackbacks - 0, articles - 0
<2010年9月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

常用鏈接

留言簿(24)

隨筆分類(332)

隨筆檔案(182)

FRIENDS

搜索

積分與排名

最新隨筆

最新評論

閱讀排行榜

評論排行榜

多重背包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種物品的數目可以是:0、1、2……,可得:

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)+w、f(4*v)+2*w 這三個中的最大值

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

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

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

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

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

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

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

 

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

假設d = v[i],a = j / d,b = 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的元素(或其所指向的元素)是單調遞減的,這應該就是《背包九講》中的提到的“單調隊列”吧,初看的時候被這個概念弄得稀里糊涂,網上的資料提到“維護隊列的最大值”,剛開始還以為是維護這個單調隊列的最大值,對其采用的算法,越看越糊涂。其實,只要明白用一個“輔助隊列”,求另一個隊列的最值,那么具體的算法,和該“輔助隊列”的性質(單調變化),都很容易推導出來。

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


 1const int MAX_V = 100004;
 2//v、n、w:當前所處理的這類物品的體積、個數、價值
 3//V:背包體積, MAX_V:背包的體積上限值
 4//f[i]:體積為i的背包裝前幾種物品,能達到的價值上限。
 5inline void pack(int f[], int V, int v, int n, int w)
 6{
 7  if (n == 0 || v == 0return;
 8  if (n == 1{               //01背包
 9    for (int i = V; i >= v; --i)
10      if (f[i] < f[i - v] + w) f[i] = f[i - v] + w;
11    return;
12  }

13  if (n * v >= V - v + 1{   //完全背包(n >= V / v)
14    for (int i = v; i <= V; ++i)
15      if (f[i] < f[i - v] + w) f[i] = f[i - v] + w;
16    return;    
17  }

18
19  int va[MAX_V], vb[MAX_V];   //va/vb: 主/輔助隊列
20  for (int j = 0; j < v; ++j) {     //多重背包
21    int *pb = va, *pe = va - 1;     //pb/pe分別指向隊列首/末元素
22    int *qb = vb, *qe = vb - 1;     //qb/qe分別指向輔助隊列首/末元素  
23    for (int k = j, i = 0; k <= V; k += v, ++i) {
24      if (pe  == pb + n) {       //若隊列大小達到指定值,第一個元素X出隊。
25        if (*pb == *qb) ++qb;   //若輔助隊列第一個元素等于X,該元素也出隊。 
26        ++pb;
27      }

28      int tt = f[k] - i * w;
29      *++pe = tt;                  //元素X進隊
30      //刪除輔助隊列所有小于X的元素,qb到qe單調遞減,也可以用二分法
31      while (qe >= qb && *qe < tt) --qe;
32      *++qe = tt;              //元素X也存放入輔助隊列        
33      f[k] = *qb + i * w;      //輔助隊列首元素恒為指定隊列所有元素的最大值
34    }

35  }

36}

37
38



多重背包特例:物品價值和體積相等(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。



 1const int MAX_V = 100004;
 2//w = v 特例
 3inline void pack(bool f[], int V, int v, int n)
 4{
 5  if (n == 0 || v == 0return;
 6  if (n == 1{  //01背包
 7    for (int i = V; i - v >= 0--i)
 8      if (! f[i] && f[i - v]) f[i] = true;
 9      //if (f[i - v]) f[i] = true;
10    return;
11  }

12  if (n * v >= V - v + 1{  //完全背包 n >= V / v
13    for (int i = v; i <= V; ++i)
14      if (! f[i] && f[i - v]) f[i] = true;
15      //if (f[i - v]) f[i] = true;      
16    return;
17  }

18  
19   bool va[MAX_V];
20    for (int j = 0; j < v; ++j) {     //多重背包
21    bool *pb = va, *pe = va - 1
22    size_t sum = 0;
23    for (int k = j; k <= V; k += v) {
24      if (pe == pb + n) sum -= *pb++;  //隊列已滿,隊首元素出隊
25      *++pe = f[k];  //進隊
26      sum += f[k];     
27      if (! f[k] && sum != 0) f[k] = true
28      //f[k] = (bool)sum;       
29    }

30  }

31}

32

 

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



 1//w = v 特例
 2inline void pack(bool f[], int V, int v, int n)
 3{
 4  if (n == 0 || v == 0return;
 5  if (n == 1{  //01背包
 6    for (int i = V; i - v >= 0--i)
 7      if (! f[i] && f[i - v]) f[i] = true;
 8      //if (f[i - v]) f[i] = true;
 9    return;
10  }

11  if (n * v >= V - v + 1{  //完全背包 n >= V / v
12    for (int i = v; i <= V; ++i)
13      if (! f[i] && f[i - v]) f[i] = true;
14      //if (f[i - v]) f[i] = true;      
15    return;
16  }

17  
18  for (int j = 0; j < v; ++j) {    //多重背包
19    int k = V - j, sum = 0;
20    //前n + 1個元素入隊,前面的判斷可以保證: V - j - n * v > 0
21    for (; k >= std::max(0, V - j - n * v); k -= v) sum += f[k];
22    for (int i = V - j; i > 0; k -= v, i -= v) {
23      if (f[i]) --sum;      //出隊: sum -= f[i] 
24      else if (sum != 0) f[i] = true;
25      //int tt = f[i]; f[i] = (bool)sum; sum -= tt;
26      if (k >= 0) sum += f[k];      
27    }

28  }
 
29}

30

 

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

 

對該特例,還有一種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數組和后面的循環整合在一起。



1//pack-1  
2for (int i = 0; i <= V; ++i)  count[i] = 0;
3for (int i = v; i <= V; ++i) {     //多重背包
4  if (! f[i] && f[i - v] && count[i - v] < n) {
5    count[i] = count[i - v] + 1
6    f[i] = true;
7  }

8}

9


 1//pack-2  cur為當前這類物品的編號
 2for (int i = v; i <= V; ++i) {     //多重背包
 3  if (! f[i] && f[i - v]) {
 4    if (last[i - v] != cur)  count[i] = 1, last[i] = cur, f[i] = true;
 5    else if (count[i - v] < n) {
 6      count[i] = count[i - v] + 1
 7      last[i] = cur;
 8      f[i] = true;
 9    }

10  }

11}

12


 1//pack-4
 2for (int i = v; i <= V; ++i) {     //多重背包
 3  if (f[i]) count[i] = 0;
 4  else if (f[i - v]) {
 5    if (i < 2 * v)  count[i] = 1, f[i] = true;  
 6    else if (count[i - v] < n) {
 7      count[i] = count[i - v] + 1;
 8      f[i] = true;
 9    }
  
10  }

11}

12


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



 1#include<algorithm>
 2#include<cstdio>
 3#define AB 1
 4
 5//MAX_N 物品種類數最大值 MAX_n每種物品數目的最大值,MAX_V背包體積最大值
 6const int MAX_N = 101, MAX_V = 100004;
 7
 8//w = v特例
 9inline void pack(bool f[], int V, int v, int n, int& total)
10{
11  //if (n == 0 || v == 0) return;
12  if (n == 1{  //01背包
13    for (int i = V; i - v >= 0--i)
14      if (! f[i] && f[i - v]) f[i] = true++total;
15    return;
16  }

17  if (n * v >= V - v + 1{  //完全背包 n >= V / v
18    for (int i = v; i <= V; ++i)
19      if (! f[i] && f[i - v]) f[i] = true++total;
20    return;
21  }

22
23  for (int j = 0; j < v; ++j) {    //多重背包
24    int k = V - j, sum = 0;
25    //前n + 1個元素入隊,前面的判斷可以保證: V - j - n * v > 0
26    for (; k >= V - j - n * v; k -= v) sum += f[k];
27    for (int i = V - j; i > 0; k -= v, i -= v) {
28      if (f[i]) --sum;      //出隊: sum -= f[i] 
29      else if (sum != 0) f[i] = true++total;
30      //int tt = f[i]; f[i] = (bool)sum; sum -= tt;
31      if (k >= 0) sum += f[k];      
32    }

33  }

34}

35
36struct Node {
37  int n, v, V;
38  bool operator<(const Node& other) const return V < other.V;} 
39}
;
40
41int main()
42{
43#if AB == 1
44  freopen("src.txt","r",stdin);
45  freopen("z-b.txt","w",stdout);
46#endif  
47  Node node[MAX_N];
48  int V, N;
49  bool f[MAX_V];
50  while (scanf("%d %d",&N,&V) != EOF) {
51    if (N + V == 0break;
52    Node *np = node + N;
53    for (Node *= node; p < np; ++p) scanf("%d"&p->v);
54    for (Node *= node; p < np; ++p) {
55      scanf("%d"&p->n);
56      p->= std::min(p->* p->v, V);
57    }

58    std::sort(node, np);
59    int total = 0;
60    f[0= true;
61    for (int i = 1; i <= V; ++i) f[i] = false;
62    int mv = 0;
63    for (Node *= node; p < np && total < V; ++p) {
64      mv = std::min(mv + p->V, V);
65      pack(f,mv,p->v,p->n, total);
66    }

67    printf("%d\n",total);
68  }

69}

70

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

 


還有一種特例:

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

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

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            精品999在线观看| 亚洲欧美国产视频| 亚洲人成网站999久久久综合| 欧美日韩免费观看一区二区三区| 亚洲精品久久久久中文字幕欢迎你| 亚洲欧洲另类| 国产丝袜一区二区| 亚洲自拍偷拍一区| 亚洲美女精品一区| 欧美日韩在线精品一区二区三区| 亚洲午夜在线观看视频在线| 女同性一区二区三区人了人一| 久久av一区二区三区| 亚洲综合视频在线| 在线视频欧美一区| 日韩视频在线观看| 国产精品人人做人人爽| 国产欧美1区2区3区| 国产精品裸体一区二区三区| 欧美日韩国产成人在线观看| 久久精品成人一区二区三区蜜臀| 亚洲免费久久| 性欧美xxxx大乳国产app| 欧美一区二区黄色| 免费观看一区| 欧美国产亚洲另类动漫| 久久久久久国产精品mv| 久久国产精品免费一区| 久久夜色精品| 国产精品亚洲第一区在线暖暖韩国| 欧美视频免费| 国产亚洲欧美另类中文 | 国产深夜精品| 精品动漫av| 久久成人精品一区二区三区| 久久亚洲美女| 一级日韩一区在线观看| 99国产精品国产精品久久| 日韩午夜免费| 欧美三级日本三级少妇99| 欧美精品一区在线观看| 欧美日韩精品一区二区天天拍小说| 美女尤物久久精品| 亚洲七七久久综合桃花剧情介绍| 欧美一区二区在线免费观看| 亚洲国产精品一区二区第一页| 国产精品99久久久久久久女警| 亚洲一区自拍| 欧美三级电影精品| 亚洲私人影吧| 久久亚洲高清| 亚洲最新合集| 欧美成人一区二区三区片免费| 99视频日韩| 亚洲激情网站| 亚洲自拍偷拍视频| 欧美色大人视频| 在线高清一区| 国产主播一区二区三区| 亚洲少妇最新在线视频| 久久琪琪电影院| 亚洲视频网在线直播| 欧美日韩国产成人在线观看| 亚洲国产精品传媒在线观看| 亚洲日本在线观看| 嫩草国产精品入口| 亚洲综合丁香| 精品91免费| 久久久久久**毛片大全| 久久久久久噜噜噜久久久精品| 欧美三级电影大全| 欧美国产精品一区| 亚洲无亚洲人成网站77777| 久久精品91| 国产精品无人区| 亚洲国产精品成人va在线观看| 亚洲欧美中文另类| 亚洲一区二区三区欧美 | 久久久久久欧美| 亚洲欧美激情诱惑| 国产视频欧美视频| 久久一二三四| 欧美成人综合| 亚洲在线1234| 一本一道久久综合狠狠老精东影业| 欧美精品一区二区视频| 亚洲伦理一区| 久久成人免费视频| 欧美国产亚洲精品久久久8v| 久久亚洲视频| 亚洲在线视频网站| 国产精品乱子久久久久| 在线免费观看日本一区| 亚洲欧美一区二区激情| 欧美精品激情在线观看| 久久久久久久一区| 国产一区二区欧美日韩| 亚洲欧美在线aaa| 久久精品日韩一区二区三区| 国产亚洲成精品久久| 一区二区精品| 国产精品日韩| 久久久久五月天| 亚洲欧美色一区| 国产一区久久久| 欧美一区午夜视频在线观看| 久久gogo国模啪啪人体图| 欧美性事免费在线观看| 亚洲日本免费电影| 国产综合久久久久久鬼色| 精品成人免费| 欧美成人在线免费视频| 欧美激情1区2区| 亚洲一区二区3| 亚洲综合好骚| 亚洲午夜电影| 欧美激情精品久久久久久大尺度 | 国产精品久久久久久福利一牛影视| 久久狠狠久久综合桃花| 国产在线精品成人一区二区三区| 午夜久久电影网| 亚洲一区二区视频| 亚洲伊人伊色伊影伊综合网| 欧美日韩亚洲高清| 欧美一区激情| 亚洲伦理久久| 亚洲欧美美女| 久久九九免费视频| 国产麻豆精品在线观看| 欧美国产视频在线| 午夜老司机精品| 欧美大色视频| 亚洲精选视频免费看| 亚洲欧洲精品一区二区精品久久久| 欧美成人tv| 蜜臀久久99精品久久久久久9| 久久精品一本久久99精品| 亚洲国产精品一区二区久| 新67194成人永久网站| 99国产精品国产精品久久| 亚洲第一成人在线| 亚洲精品视频在线观看网站| 一区电影在线观看| 亚洲伦理在线| 亚洲国产99| 一二美女精品欧洲| 浪潮色综合久久天堂| 久久国产精品久久久| 亚欧成人精品| 久久亚洲视频| 一区二区三区四区在线| 亚洲第一色中文字幕| 免费欧美电影| 欧美成年人网站| 欧美一区二区高清在线观看| 国产精品久久久久久久久免费樱桃| 99精品视频一区| 亚洲自拍偷拍一区| 日韩视频在线观看一区二区| 欧美在线|欧美| 欧美高清在线精品一区| 久久高清免费观看| 久久字幕精品一区| 欧美日韩成人精品| 亚洲高清视频一区| 亚洲天堂网在线观看| 欧美成在线观看| 欧美电影免费观看大全| 狠狠88综合久久久久综合网| 欧美激情一区二区三区高清视频| 久久久国产一区二区| 欧美日韩美女在线观看| 国产伦精品一区二区三区在线观看 | 国产主播一区二区| 亚洲欧美日韩一区二区三区在线观看 | 一区二区三区欧美激情| 欧美性猛交xxxx乱大交退制版| 亚洲大片在线观看| 亚洲美女毛片| 精品动漫3d一区二区三区免费版 | 国产精品一区二区黑丝| 久久一区二区三区国产精品| 国产精品久久久久久五月尺| 亚洲女ⅴideoshd黑人| 午夜亚洲福利在线老司机| 国产一区二区在线观看免费| 亚洲国产精品t66y| 国产美女精品免费电影| 久久精品国产91精品亚洲| 欧美激情精品久久久久久| 久久久夜色精品亚洲| 国产欧美日韩不卡免费| 欧美激情一区二区三区成人| 国产精品亚洲综合色区韩国| 亚洲国产91精品在线观看| 亚洲激情影视| 久久国产精品99精品国产| 久久激情五月激情| 韩国精品在线观看| 你懂的国产精品永久在线|