NYOJ 1091 超大01背包(折半枚举)

这道题乍一看是普通的01背包,最最基础的,但是仔细一看数据,发现普通的根本没法做,仔细观察数组发现n比较小,利用这个特点将它划分为前半部分和后半部分这样就好了,当时在网上找题解,找不到,后来在挑战程序设计上找到了这个题,就拿来引用一下

 

挑选物品的方法总从2^n中,直接枚举肯定不行,因为n最大为40,但是如果n为20就可以了,这时候就要用到折半枚举,先枚举前一半,在枚举后一半。先把前把部分的选取方法对应的重量和价值总和记为w1, v1,这样后半部分寻找w2 <= W - w1时 使v2最大的选取方法就好了。

因此,我们要思考从枚举得到的(w2, v2)的集合中高效寻找max{v2|w2<=W'}的方法。首先,显然我们可以排除所有w2[i] <= w2[j]并且v2[i] >= v2[j] 的 j。 这一点可以按照w2,v2的字典序排列后做到。此后剩余的元素都满足w2[i] < w2[j] , v2[i] < v2[j], 要计算max{v2|w2<=W'}的话,只要寻找满足w2[i]<=W'的最大的i就可以了。这可以利用二分搜索完成。剩余的元素个数为M的话,一次搜索要用log(M)的时间,可以解决

代码如下:

方法一(折半枚举):

 1  
 2 #include <iostream>
 3 #include <algorithm>
 4 #include <cstdio>
 5 #define Max(a,b) a>b?a:b
 6 #define INF 10000000000000000
 7 using namespace std;
 8 typedef long long LL;
 9 const int MAX = 40;
10 LL weight[MAX], value[MAX];
11 LL W;
12 pair<LL, LL> ps[1 << (MAX / 2)];
13 int n;
14 void slove()
15 {
16     //枚举前半部分 
17     int n2 = n / 2;
18     for (int i = 0; i < 1 << n2; i++)//前半部分的枚举总数为 2^(n/2); 
19     {
20         LL sw = 0, sv = 0;
21         //每种结果选取特定的价值和重量(i.e 一共2个东西,就一共四种情况,都不选,选第一个,选第二个,都选) 
22         for (int j = 0; j < n2; j++)
23         {
24             if (i >> j & 1)
25             {
26                 sw += weight[j];
27                 sv += value[j];
28             }
29         }
30         ps[i] = make_pair(sw, sv);//加入到ps数组中 
31     }
32     //对ps排序 
33     sort(ps, ps + (1 << n2));
34     //ps 去重 
35     int m = 1;
36     for (int i = 1; i < 1 << n2; i++)
37         if (ps[m - 1].second < ps[i].second)
38             ps[m++] = ps[i];
39     LL res = 0;//保存结果 
40     //枚举后半部分, 并且找到最优解 
41     for (int i = 0; i < 1 << (n - n2); i++)//同样枚举的总个数 
42     {
43         LL sw = 0, sv = 0;
44         for (int j = 0; j < n - n2; j++)//和前半部分的一样 
45         {
46             if (i >> j & 1)
47             {
48                 sw += weight[n2 + j];
49                 sv += value[n2 + j];
50             }
51         }
52         if (sw <= W)//加个判断求解最大价值,只有小于背包容量的时候 
53         {
54             LL tv = (lower_bound(ps, ps + m, make_pair(W - sw, INF)) - 1)->second;//找到前半部分对应的value 
55             res = Max(res, sv + tv); 
56         }
57     }
58     printf("%lld\n", res);
59 }
60 
61 int main()
62 {
63     while (~scanf("%d %lld", &n, &W))
64     {
65         for (int i = 0; i < n; i++)
66             scanf("%lld %lld", &weight[i], &value[i]);
67         slove();
68     }
69     return 0;
70 }
71         

这个题也可以用搜做来做,搜索反而来的更快,因为n比较小

方法二(搜索):

 1 #include <stdio.h>
 2 #include <string.h>
 3 #define Max(a, b) a > b ? a : b
 4 const int MAX = 45;
 5 long long weight[MAX], value[MAX], sw[MAX], sv[MAX];
 6 long long W, n, ans;
 7 //i表示当前取到第n-i个,cnt 表示当前的总value, w当前背包剩余的空间 
 8 void dfs(int i, long long cnt, long long w)
 9 {
10     if (i == 0)//取到最后 
11     {
12         ans = Max(ans, cnt);
13         return;
14     }
15     if (w == 0 || cnt + sv[i] < ans)//背包满或者当前总的加上这个前i个的总价值小于当前的总value,这步是剪枝 
16         return ;
17     if (w >= sw[i])//因为是从上往下找的,所以只要当前容量能装下前i个的和,所以这时一定是最大的 
18     {
19         cnt += sv[i];
20         ans = Max(ans, cnt);
21         w = 0;
22         return ; 
23     }
24     if (w > weight[i])//深搜两种状态 
25         dfs(i - 1, cnt + value[i], w - weight[i]);//相当于01背包中的两种状态 
26     dfs(i - 1, cnt, w);
27 } 
28 int main()
29 {
30     while (~scanf("%d %lld", &n, &W))
31     {
32         memset(sw, 0, sizeof(weight));
33         memset(sv, 0, sizeof(value));
34         ans = 0;
35         for (int i = 1; i <= n; i++)
36         {
37             scanf("%lld %lld", &weight[i], &value[i]);
38             sw[i] = sw[i - 1] + weight[i];
39             sv[i] = sv[i - 1] + value[i];
40         }
41         dfs(n, 0, W);
42         printf("%lld\n", ans);
43     }
44     
45     return 0;
46 } 

 

posted @ 2014-12-14 11:01  Howe_Young  阅读(863)  评论(0编辑  收藏  举报