挑战程序设计竞赛 折半枚举

折半枚举的思想就是如果直接暴力复杂度太大,我们可以先枚举一半,再通过这些值再枚举另外一半算出结果。

传送门
poj 2785 4 Values whose Sum is 0

题意:有四个数组,每个数组的大小都为n,要求从各个数组中各找一个数使a + b + c + d = 0;

思路:a + b + c + d = 0 -> a + b = -(c + d), 我们可以枚举a数组和b数组所有和的结果,然后再枚举c + d的同时用二分找到符合结果的个数。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;

typedef long long LL;
typedef pair<int, int> P;

const int maxn = 4000 + 10;
const int INF = 0x3f3f3f3f;
const int MAX_LOG_N = 20;
const double eps = 1e-6;
const int mod = 1e9 + 7;

int n;
int A[maxn], B[maxn], C[maxn], D[maxn];
int CD[maxn * maxn];

void solve() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d %d %d %d", &A[i], &B[i], &C[i], &D[i]);

    //折半枚举C + D
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            CD[i + (j-1) * n] = C[i] + D[j];
        }
    }    
    sort(CD+1, CD+1+n*n);
    LL res = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            int t = -(A[i] + B[j]);
            //与t相等的个数
            res += upper_bound(CD+1, CD+1+n*n, t) - lower_bound(CD+1, CD+1+n*n, t);
        }
    }
    printf("%lld\n", res);
}


int main() {
    //ios::sync_with_stdio(false);
    int t = 1;
    while(t--) {
        solve();
    }
    return 0;
}

超大背包

由于这题的n很小,但w特别大,用01背包无法求解,我们可以先考虑枚举一半物品的的所有结果(2^(n/2)),同上题,在枚举另外一半的同时找到和不超过W但v最大的前一半里的结果。排序后做一个处理就行了。注意:对多个权值时,lower_bound是根据字典序来查找的。

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<LL, LL> P;

const int maxn = 40;
const LL INF = 1e17;
const double eps = 1e-6;
const LL mod = 1e9 + 7;

int n;
LL W;
LL v[maxn], w[maxn];

P ps[1 << 20];

void solve() {         
    scanf("%d", &n);
    int n2 = n / 2;
    for(int i = 0; i < n; i++) scanf("%lld", &w[i]);
    for(int i = 0; i < n; i++) scanf("%lld", &v[i]);
    scanf("%lld", &W);

    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;
    //保证v随i的增大而增大。
    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];
            }
        }
        LL temp = 0;
        if(sw <= W) {
            temp = (lower_bound(ps, ps + m, make_pair(W - sw,INF)) - 1)->second;
            res = max(res, temp + sv);
        }
    }
    printf("%lld\n", res);
}   

int main() {
    int t = 1;
    while(t--) {
        solve();
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值