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

Posted TURNINING

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了挑战程序设计竞赛 折半枚举相关的知识,希望对你有一定的参考价值。

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

传送门
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;
}

以上是关于挑战程序设计竞赛 折半枚举的主要内容,如果未能解决你的问题,请参考以下文章

长安大学第四届“迎新杯”程序设计竞赛 F 打铁的箱子数学/进制思维/折半枚举

第1章蓄势待发准备篇

AOJ 0033 Ball 题解 《挑战程序设计竞赛》

挑战程序设计竞赛 2.1 最基础的“穷竭搜索”

挑战程序设计竞赛准备篇---三角形(贪心)

POJ 3187 Backward Digit Sums 题解 《挑战程序设计竞赛》