折半枚举——poj3977

暴力搜索超时,但是折半后两部分状态支持合并的情况,可用折半枚举算法

poj3977 给一个序列a[],从里面找到k个数,使其和的绝对值最小

经典折半枚举法+二分解决,对于前一半数开一个map,map[sum]里存下凑出当前sum的最小元素个数

枚举后面一半的所有情况,然后lower_bound去找map里最接近-sum的元素,由于要求输出sum最小并且num也尽量小的答案,所以用pair来存答案

#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<math.h>
#include<vector>
#include<map>
using namespace std;
#define N 45
#define PI 4*atan(1.0)
#define mod 1000000007
#define met(a, b) memset(a, b, sizeof(a))
#define INF 10000000000000000
typedef long long LL;

int n;
LL a[N];

LL Abs(LL x){return x<0?-x:x;}
int main(){
    while(scanf("%d", &n), n){
        for(int i=0; i<n; i++)
            scanf("%I64d", &a[i]);

        map<LL, int> M;
        map<LL, int>::iterator it;
        pair<LL, int> ans(Abs(a[0]), 1);

        for(int i=1; i<1<<(n/2); i++){
            LL sum = 0;int cnt = 0;
            for(int j=0; j<(n/2); j++){
                if((i>>j)&1){
                    sum += a[j];
                    cnt ++;
                }
            }
            ans = min(ans, make_pair(Abs(sum), cnt));///全部是前半部分的;
            if(M[sum])///更新cnt为小的;
                M[sum] = min(M[sum], cnt);
            else
                M[sum] = cnt;
        }

        for(int i=1; i<1<<(n-n/2); i++){
            LL sum = 0;int cnt = 0;
            for(int j=0; j<(n-n/2); j++){
                if((i>>j)&1){
                    sum += a[j+n/2];
                    cnt ++;
                }
            }
            ans = min(ans, make_pair(Abs(sum), cnt));///全部是后半部分的;

            it = M.lower_bound(-sum);///找到第一个大于-sum的位置,然后取两种情况的最小值;

            if(it != M.end())
                ans = min(ans, make_pair(Abs(sum+it->first), cnt+it->second));
            if(it != M.begin()){
                it--;
                ans = min(ans, make_pair(Abs(sum+it->first), cnt+it->second));
            }
        }
        printf("%I64d %d\n", ans.first, ans.second);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/zsben991126/p/11396843.html

时间: 2025-01-12 02:41:08

折半枚举——poj3977的相关文章

poj3977 折半枚举

传送门:https://vjudge.net/problem/POJ-3977 题意:给你n数(n<=35),从中选出一个非空子集,使得这个子集的所有元素的值的和的绝对值最小,如果有多组数据满足的话,选择子集元素最少的那个. 这题是从挑战程序设计竞赛来的.就是折半枚举.也就是我先分别枚举前面一半的选不选状态(最多2的17次方,O(能过)),和后面一半的,得到两个子集和的数列.子集要么是只从前面一半选,要么只从后面一半选,要么从前面和后面.前面两种情况可以在枚举的时候搞了.至于最后那种情况,我们可

Load Balancing 折半枚举大法好啊

Load Balancing 给出每个学生的学分.   将学生按学分分成四组,使得sigma (sumi-n/4)最小.         算法:   折半枚举 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #include <string> 7 #include <

lightoj 1235 Coin Change (IV)(折半枚举)

话说这是俺们学校暑假集训完的一道题,刚看到以为是到水题,后来发现者复杂度也太大了,受不了了,比赛完也没搞出来,然后欣爷说这是折半枚举.然后就摸摸的学了一下,又把这道题写了一下, 所谓折半枚举就是先算出来一半,再算一半,然后用二分查找看看能不能搞到这一发状态,可以的话就是可以了, 题意:给你两个数n,k,下面再给你n个数,表示你现在有的硬币的面值,每种硬币面值的有两个,看是否可以支付k 题解思路:首先以为只有三种状态,直接dfs就好了,后来发现复杂度太大了,然后死了撸就是上面的,详细情残考代码 源

CSU OJ PID=1514: Packs 超大背包问题,折半枚举+二分查找。

1514: Packs Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 61  Solved: 4[Submit][Status][Web Board] Description Give you n packs, each of it has a value v and a weight w. Now you should find some packs, and the total of these value is max, total of

HDU 5340 Three Palindromes( 折半枚举+Manacher+记录区间 )

Three Palindromes Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 809    Accepted Submission(s): 240 Problem Description Can we divided a given string S into three nonempty palindromes? Input F

Eqs 折半枚举+二分查找 大水题

Eqs 题目抽象:a1x13+ a2x23+ a3x33+ a4x43+ a5x53=0 (*),给出a1,a2,a3,a4,a5.    ai属于[-50,50]. 求有多少序列   x1,x2,x3,x4,x5 ,xi属于 [-50,50]-{0}. 思路:折半枚举+二分查找 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #inclu

poj 2785 4 Values whose Sum is 0 折半枚举

题目链接:http://poj.org/problem?id=2785 枚举的一般思路就是先把所有的状态枚举出来 最后一次性判断该状态合法不合法 而折半枚举的思想是 先枚举一半的状态 把他们的状态存起来 排序 然后再枚举剩下一般 用目标反推前一半的期望状态 接下来在前一半的结果数组中查找是否有相应结果 之所以能优化是因为结果数组有序 就可以用二分搜索 复杂度从O(n^2 * n^2) 降到 O(n^2 * log(n^2))即(O(n^2 * log n)) 二分搜索的一个技巧 在有序数组中用二

LightOj 1076 - Get the Containers (折半枚举 绝世好题)

题目链接: http://www.lightoj.com/volume_showproblem.php?problem=1076 题目描述: 给出n个数,要求分成m段,问这m段中最大的总和,最小是多少? 解题思路: 二分每一段的长度,然后判定枚举长度是否合法. 刚看到这个题目就想到了dp,但是dp的复杂度还是很高的,然后就一直放着,放着....学了折半枚举,拿起来一看,哇塞,简直惊喜啊! 1 #include<cstdio> 2 #include<cstring> 3 #inclu

NYOJ 1091 超大01背包(折半枚举)

这道题乍一看是普通的01背包,最最基础的,但是仔细一看数据,发现普通的根本没法做,仔细观察数组发现n比较小,利用这个特点将它划分为前半部分和后半部分这样就好了,当时在网上找题解,找不到,后来在挑战程序设计上找到了这个题,就拿来引用一下 挑选物品的方法总从2^n中,直接枚举肯定不行,因为n最大为40,但是如果n为20就可以了,这时候就要用到折半枚举,先枚举前一半,在枚举后一半.先把前把部分的选取方法对应的重量和价值总和记为w1, v1,这样后半部分寻找w2 <= W - w1时 使v2最大的选取方