折半枚举(双向搜索)

1.

  

 

  从 4 个数列中选择的话总共有 n4 种情况,所以全都判断一遍不可行。不过将它们对半分成 AB 和 CD 再考虑,就可以快速解决了。从两个数列中选择的话只有 n2 种组合,所以可以进行枚举。先从 A、B中取出 a、b 后,为了使总和为 0 则需要从 C、D中取出 c + d =  - a - b。因此先将从 C、D 中取数字的 n2 种方法全部都枚举出来,将这些和排好序,这样就可以运用二分搜索。这个算法的复杂度是 O(n2 log n)。

  有时候,问题的规模比较大,无法枚举所有元素的组合,但能够枚举一半元素的组合。此时,讲问题拆分两半后分别枚举,再合并它们的结果这一方法往往非常有效。

int n;
int A[MAX_N], B[MAX_N], C[MAX_N], D[MAX_N];

int CD[MAX_N * MAX_N]; // C和D中数字的组合方法

void solve() {
    //枚举 C和D中取出数字的所有方法
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            CD[i * n + j] = C[i] + D[j];
    sort(CD, CD + n * n);
    
    long long res = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) {
            int cd = -(A[i] + B[j]);
            res += upper_bound(CD, CD + n * n, cd) - lower_bound(CD, CD + n * n, cd);
        }
    printf("%lld\n", res);
} 

 2.

  

  

  这个问题显然是背包问题。不过这次价值和重量都非常大,相比之下 n 比较小。使用 DP 求解背包问题的复杂度为 O(n W),因此不能用来解决这里的问题。此时我们应该利用 n 比较小的特点来寻找其他办法。

  挑选物品的方法总共有 2n 种,所有不能直接枚举,但是像前面一样拆成两半之后再枚举的话,因为每个部分只有 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 的话,一次搜索需要 O(log M)的时间。因为 M ≤ 2(n / 2),所以这个算法的复杂度是O(2(n / 2)n)。

typedef long long ll;

int n;
ll w[MAX_N], v[MAX_N];
ll W;

pair<ll, ll> pa[1 << (MAX_N / 2)];  // (重量,价值)

void solve() {
    //枚举前半部分
    int n2 = n / 2;
    for (int i = 0; i < 1 << n2; i++) {
        ll sw = 0, sv = 0;
        for (int j = 0; j < n2; j++)
            if (i >> j & 1) {
                sw += w[j];
                sv += v[j];
            }
        ps[i] = make_pair(sw, sv);
    } 
    // 去除多余元素
    sort(ps, ps + (1 << n2));
    int m = 1;
    for (int i = 1; i < 1 << n2; i++)
        if (ps[m - 1].second < ps[i].second) 
            ps[m++] = ps[i];
    // 枚举后半部分并求解
    ll res = 0;
    for (int i = 0; i < 1 << (n - n2); i++) {
         ll sw = 0, sv = 0;
         for (int j = 0; j < n - n2; j++)
             if (i >> j & 1) {
                 sw += w[n2 + j];
                 sv += v[n2 + j];
            }
        if (sw <= W) {
            ll tv = (lower_bound(ps, ps + m, make_pair(W - sw, INF)) - 1)->second;
            res = max(res, sv + tv);
        }
    }
    printf("%lld\n", res); 
} 

 

枚举方面:

#include<iostream>
#include<algorithm>
using namespace std;
int main() {
    for (int i = 0; i < 1 << 4; i++)
        for (int j = 0; j < 4; j++)
            printf("%d%c", i >> j & 1, j == 3 ? '\n' : ' ');
    return 0;
    
}

 

posted @ 2019-05-13 21:54  莫莫君不恋爱  阅读(355)  评论(0编辑  收藏  举报