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 }