【DP】【单调队列--多重背包】

【01背包模板】

 

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int Maxv = 1001;
 7 int N, V, F[Maxv];
 8 
 9 int main()
10 {
11     cin >> N >> V;
12     for (int i = 1, w, c; i <= N; i ++)
13     {
14         cin >> w >> c;
15         for (int j = V; j >= w; j --)
16             F[j] = max(F[j], F[j - w] + c);
17     }
18     cout << F[V] << endl;
19     return 0;
20 }

 

 

【完全背包模板】

 

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int Maxv = 1001;
 7 int N, V, F[Maxv];
 8 
 9 int main()
10 {
11     cin >> N >> V;
12     for (int i = 1, w, c; i <= N; i ++)
13     {
14         cin >> w >> c;
15         for (int j = w; j <= V; j ++)
16             F[j] = max(F[j], F[j - w] + c);
17     }
18     cout << F[V] << endl;
19     return 0;
20 }

 

 

【多重背包(二进制拆分)模板】

 

 

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 const int Maxv = 1001;
 7 int N, V, F[Maxv];
 8 
 9 int main()
10 {
11     cin >> N >> V;
12     for (int i = 1, p, w, c; i <= N; i ++)
13     {
14         cin >> p >> w >> c; p = min(p, V / w);
15         int m = 1;
16         while (p)
17         {
18             if (m > p) m = p; p -= m;
19             for (int j = V; j >= m * w; j --)
20                 F[j] = max(F[j], F[j - m * w] + m * c);
21             m *= 2;
22         }
23     }
24     cout << F[V] << endl;
25     return 0;
26 }

 

 

【二进制拆分解析】本质是将多重背包转化为01背包,然后运用01背包的状态进行转移。对于物品I,假设它的数量有PI,将PI进行拆分。注意:此处并非是将PI拆成PI个物品,而是运用二进制的思想,将PI拆分为1,2,2^2,2^3,...,2^k,PI-2^k(这个物品要注意到)这些数量的物品,(物品的价值等于数量*个数),因为通过这些物品的01组合,可以自然地得出0..PI所有数量的物品个数。所以,问题就转化成了01背包的求解,只要将这些物品看成是普通的01背包中的物品即可。

         有个小优化是p=min(p, V/w);p表示的是这个物品的数量。

 

【多重背包(单调队列)模板】

 

 

 1 #include <iostream>
 2 #include <deque>
 3 #include <algorithm>
 4 
 5 using namespace std;
 6 
 7 struct Pack
 8 {
 9     int sum, cost;
10     Pack(int _s, int _c) : sum (_s), cost(_c) {}
11 };
12 
13 const int Maxv = 1001;
14 deque <Pack> Q;
15 int N, V, F[Maxv];
16 
17 int main()
18 {
19     cin >> N >> V;
20     for (int i = 1, p, w, c; i <= N; i ++)
21     {
22         cin >> p >> w >> c; p = min(p, V / w);
23         for (int j = 0; j < w; j ++)
24         {
25             Q.clear();
26             for (int k = 0; k <= (V - j) / w; k ++)
27             {
28                 int y = F[k * w + j] - k * c;
29                 while (Q.size() && Q.back().cost <= y) Q.pop_back();
30                 Q.push_back(Pack(k, y));
31                 if (Q.front().sum < k - c) Q.pop_front();
32                 F[k * w + j] = Q.front().cost + k * c;
33             }
34         }
35     }
36     cout << F[V] << endl;
37     return 0;
38 }

 

【单调队列解析】

V表示背包总容量,Wi表示当前物品的重量,Ci表示当前物品的价值,Pi表示当前物品的数量。

最朴素的多重背包转移方程为:F[I][V] = Max{F[I - 1][V - K * Wi] + K * Ci} 0<=k<=min(Pi, V / W)

现在对其进行分析,F[I][V]可以从F[I-1][V-Wi],F[I-1][V-2Wi],F[I-1][V-3Wi]...转移而来

         F[I][V-Wi]可以从F[I-1][V-2Wi],F[I-1][V-3Wi] ...转移而来。

可以看到,这2者之间存在着重复的过程,因此可以进行优化。方法如下:

假设Wi=3,将V对Wi的余数进行编号:

         

上一排表示从0..V的体积,下一排表示对Wi的余数。

接着进行分组:将余数相同的体积分为一组!对于余数为d的这一组,我们进行分析:

     F[I][d], F[I][d + Wi], F[I][d + 2Wi],F[I][d+3Wi]...

以上这些体积进行状态转移时候只需要借助的是:

      F[I-1][d], F[I-1][d + Wi], F[I-1][d + 2Wi],F[I-1][d+3Wi]...

由于F[I][]要反复地借用F[I-1][]这些状态,因此想到将F[I-1][]的这些状态放入一个队列,倘若这个队列每次可以通过O(1)时间取出F[I-1][]中最优的决策,那么问题就得到本质的优化。

此处要注意一个问题,就是F[I-1][d], F[I-1][d + Wi], F[I-1][d + 2Wi],F[I-1][d+3Wi]...这些状态互相之间是没有可比性的!因为体积大的自然价值也就会大。

将它们转变为F[I-1][d], F[I-1][d + Wi]-Ci, F[I-1][d + 2Wi]-2Ci,F[I-1][d+3Wi]-3Ci

这样做的目的在于使得每个状态相当于体积为d,然后互相之间就具有了可比性。因此,这之中最优的那个状态就可以用来进行F[I][]的转移。

 

下面解释一下单调队列为什么单调以及它的维护过程:

队列中的元素需要记录2个域,1个是sum,为F[I-1][d + sum*Wi]中的那个sum,表示它是在余数为d的基础上,多放了sum个Wi。另1个是cost,即F[I-1][d + sum*Wi]-sum*Ci。

每次在求F[I][d+sum*Wi]的时候,需要将F[I-1][d],F[I-1][d+Wi]-Ci,F[I-1][d+2Wi]-2Ci,F[I-1][d+3Wi]-3Ci...F[I][d+sum*Wi]-sum*Ci放进队列中。然后取出其中的最大值,然后再加上sum*Ci即可。

每次循环到当前的F[I][d+K*Wi]的时候,就新添加一个元素(K,F[I-1][d+K*Wi]-K*Ci)进入队列。新元素进入队列的时候,从队列的最右端开始检测,倘若遇到cost值小于等于新元素cost值的元素,那么原来的这些元素就不具有价值了。因为新元素的sum值和cost值都大于等于旧元素的sum和cost,那么在状态转移时候,新元素完全可以取代旧元素。因此删除这些旧元素。这样一来,队列就变成了cost值严格单调递减的队列。每次只需要取出队头元素更新即可。需要注意一点是,队头元素的sum+Pi之后如果仍然<当前的K,那么就要将队头元素出队了。详情看代码可知。

 

 

 

posted on 2013-01-08 15:08  孤星ぁ紫辰  阅读(1793)  评论(0编辑  收藏  举报

导航