poj3977 - subset - the second time - 暴力 + 二分

Posted pprp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了poj3977 - subset - the second time - 暴力 + 二分相关的知识,希望对你有一定的参考价值。

2017-08-26 11:38:42

writer:pprp

已经是第二次写这个题了,但是还是出了很多毛病

先给出AC代码:

解题思路:

之前在培训的时候只是笼统的讲了讲怎么做,进行二分对其中一边进行暴力枚举,对另一边用lower_bound查找算出的相反数

现在给出详细一点的思路:

答案可能在左边枚举的部分,也可能在右边枚举的部分,也可能在两边加起来的和中

所以从这三个方面不能少考虑;

还有用到了map所以算出来的key是唯一的,所以当算出来两个key相等的时候,应该采用value也就是cnt比较小的那个数

细节比较多,很容易就wa

/*
@prama: poj 3977
@writer:pprp
@declare:暴力+二分
@date:2017/8/26
*/

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
#define ll long long
#define IOS ios::sync_with_stdio(false),cin.tie(0);
#define tag cout <<"-------" << endl;

using namespace std;
int N;
const int maxn = 40;
ll subset[maxn];  //改成ll


ll ll_abs(ll a)
{
    return a > 0 ? a:- a;
}

int main()
{
      IOS;
    while(cin >> N && N)
    {
        memset(subset,0,sizeof(subset));
        for(int i = 0 ; i < N ; i++)  
                  cin >> subset[i];

        //初始化这个pair,用来记录答案
        pair<ll,int> ans(ll_abs(subset[0]),1);
        map<ll,int> mp;// sum -> cnt

        //二进制枚举前一半
        for(int i = 0 ; i < (1 << N/2) ; i++)
        {
            ll sum = 0;
            int cnt = 0;
            for(int j = 0 ; j < N/2 ; j++)
            {
                //二进制枚举,判断第j为是否被选中
                if((i >> j)&1)
                {
                    sum += subset[j];
                    cnt++;
                }
            }
            //**增加一个判断,如果一个都没有被选中
            if(cnt == 0) continue;

            //对结果进行操作,找到当前最小的sum
            ans = min(ans, make_pair(ll_abs(sum),cnt));

            //将每次枚举的情况都加入map中去
            map<ll,int>::iterator ii = mp.find(sum);
            if(ii != mp.end())//如果能找到,就采用那个比较小的cnt,因为map的key是unique的所以要进行如下判断
                ii->second = min(ii->second,cnt);
            else //如果没有找到就直接放到map中去
                mp[sum] = cnt;
        }

        //对后一半进行枚举,得到的sum可从map中寻找最接近-sum的值
        for(int i = 0 ; 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)
                {
                    cnt++;
                    sum += subset[N/2 +j];
                }
            }
            //**增加一个判断,如果一个都没有被选中
            if(cnt == 0) continue;
            //对结果进行操作,找到当前最小的sum
            ans = min(ans, make_pair(ll_abs(sum),cnt));

            //对后半段进行找最接近于-sum的值
            //运用lower_bound查找的是不小于-sum的值,就是比-sum略大的值
            map<ll,int>::iterator it = mp.lower_bound(-sum);
            if(it != mp.end())//如果可以找到就进行比较
            {
                ans = min(ans, make_pair(ll_abs(it->first + sum),it->second+cnt));
            }

           // 有可能是比-sum略小的值,这个也可以用upper_bound来写 ??
            if(it != mp.begin())
            {
                it--;
                ans = min(ans, make_pair(ll_abs(it->first + sum),it->second+cnt));
            }
            //经过测试,不可以采用这个方法
            map<ll,int>::iterator tt = mp.upper_bound(-sum);
            {
                ans = min(ans, make_pair(ll_abs(it->first + sum),it->second+cnt));
            }
        }

        cout << ans.first << " " << ans.second << endl;
    }
    return 0;
}

遇到的问题:

1、数据类型的选择,数据范围是10^15 远远超过int类型了,所以一开始没有检查数组类型,导致wa了很多次,

也浪费了很长时间。

2、lower_bound和upper_bound的用法还是不是很清楚。

最后那部分不可以用upper_bound直接将指针进行移动就可以

以上是关于poj3977 - subset - the second time - 暴力 + 二分的主要内容,如果未能解决你的问题,请参考以下文章

poj 3977 Subset

POJ3977:Subset——题解(三分+折半搜索)

POJ3977 Subset

Subset(POJ-3977)

POJ 3977 Subset | 折半搜索

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