zoukankan      html  css  js  c++  java
  • 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
    ", 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
    ", ans);
    43     }
    44     
    45     return 0;
    46 } 
  • 相关阅读:
    工厂模式
    装饰器模式
    策略模式
    代理模式
    建造者模式
    单例模式
    观察者模式
    JVM运行时数据区
    Export to excel
    C#网络编程(同步传输字符串) Part.2 [转自JimmyZhang博客]
  • 原文地址:https://www.cnblogs.com/Howe-Young/p/4162315.html
Copyright © 2011-2022 走看看