Subset---poj3977(折半枚举+二分查找)

题目链接:http://poj.org/problem?id=3977

给你n个数,找到一个子集,使得这个子集的和的绝对值是最小的,如果有多种情况,输出子集个数最少的;

n<=35,|a[i]|<=10e15

子集个数共有2^n个,所以不能全部枚举,但是可以分为两部分枚举;

枚举一半的所有情况,然后后一半二分即可;

#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;
}

时间: 2024-11-10 12:29:05

Subset---poj3977(折半枚举+二分查找)的相关文章

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

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

poj3977(折半枚举+二分查找)

题目链接:https://vjudge.net/problem/POJ-3977 题意:给一个大小<=35的集合,找一个非空子集合,使得子集合元素和的绝对值最小,如果有多个这样的集合,找元素个数最少的. 思路:显然,可以用折半搜索,分别枚举一半,最大是2的18次方,复杂度能够满足.因为集合非空,枚举时考虑只在前一半选和只在后一半选的情况.对于前一半后一半都选的情况,把前一半的结果存下来,排序,枚举后一半的时候在前一半里二分查找最合适的即可. 思路不难,实现有很多细节,最开始用dfs写得一直wa,

poj 2549 折半枚举+二分

三重循环肯定TLE,所以采用“折半枚举”的方法+二分查找来提高速度,不同的是需要保存两个下标用来判定是否有重复元素. 1 #include <algorithm> 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 using namespace std; 6 7 const int N = 1000; 8 int a[N]; 9 int n, cnt; 10 11 struct

poj3977 折半枚举

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

POJ - 3977 Subset(二分+折半枚举)

题意:有一个N(N <= 35)个数的集合,每个数的绝对值小于等于1015,找一个非空子集,使该子集中所有元素的和的绝对值最小,若有多个,则输出个数最小的那个. 分析: 1.将集合中的元素分成两半,分别二进制枚举子集并记录子集所对应的和以及元素个数. 2.枚举其中一半,二分查找另一半,不断取最小值. #pragma comment(linker, "/STACK:102400000, 102400000") #include<cstdio> #include<c

poj 3977 Subset 枚举+二分

首先分成一半2^17和2^18,并且把其中一半变成相反数,然后枚举一半二分查找另一半,在找到的位置前后也找找. 这里用到了二级排序,有很多细节要处理,不多说了. 巨坑的一个地方就是,不能用系统的abs,要自己手写,简直坑死.. #include<cstdio> #include<algorithm> #include<iostream> #include<map> using namespace std; typedef long long ll; stru

2. C#数据结构与算法 -- 查找算法(顺序查找,哈希查找,二分查找(折半),索引,二叉)

1. 顺序查找算法 ===================================================== 算法思想简单描述: 最突出的查找类型就是从记录集的开始处顺次遍历每条记录,直到找到所要的记录或者是 到达数据集的末尾.这就是所谓的顺序查找.顺序查找(也被称为线性查找)是非常容易实现 的.从数组的起始处开始,把每个访问到的数组元素依次和所要查找的数值进行比较.如果找 到匹配的数据项,就结束查找操作.如果遍历到数组的末尾仍没有产生匹配,那么就说明此数 值不在数组内. ==

折半枚举——poj3977

暴力搜索超时,但是折半后两部分状态支持合并的情况,可用折半枚举算法 poj3977 给一个序列a[],从里面找到k个数,使其和的绝对值最小 经典折半枚举法+二分解决,对于前一半数开一个map,map[sum]里存下凑出当前sum的最小元素个数 枚举后面一半的所有情况,然后lower_bound去找map里最接近-sum的元素,由于要求输出sum最小并且num也尽量小的答案,所以用pair来存答案 #include<iostream> #include<algorithm> #inc