【折半枚举+二分】POJ 3977 Subset

题目内容

Vjudge链接
给你\(n\)个数,求出这\(n\)个数的一个非空子集,使子集中的数加和的绝对值最小,在此基础上子集中元素的个数应最小。

输入格式

输入含多组数据,每组数据有两行,第一行是元素组合\(n\)(若\(n\)为0表示输入结束),第二行有\(n\)个数,表示要给出的\(n\)个数。

数据范围

\(n\le 35\)

输出格式

每组数据输出一行两个数中间用空格隔开,表示最小的绝对值和该子集的元素个数。

样例输入

1
10
3
20 100 -100
0

样例输出

10 1
0 2

思路

在学必修一的第一节课我们就知道有\(n\)个数的集合的非空子集有\(2^n-1\)个,因此此题直接枚举的话大小回到\(2^{35}-1\),显然出题人没有这么好心。
这里用到一种思想叫做对半枚举。可以想到\(2^{17}-1=131071\),比较优秀的,那么就开始对半枚举就好了。将\(n\)的数分为前\(\frac{n}{2}\)和后\(\frac{n}{2}\)进行枚举。同时可以结合二分查找。
如果是一个空集加上一个非空,那么得到的也是非空集合,当然如果两个空集加在一起肯定也还是空集。
注意下边界。

代码

#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;

ll Abs(ll x){//据说POJ不支持abs函数?
    return x > 0 ? x : -x;
}

int n;
ll a[40];
int main(){
    while (scanf("%d",&n)&&n!=0){
        for (int i=1;i<=n;++i)
            scanf("%d",a+i);
        map<ll,int> g;//一开始想用结构体结果发现结构体不能用lower_bound
        ll ans=Abs(a[1]);
        int len=n;

        for (int i=1;i<(1<<(n/2));++i){
            ll sum=0;
            int j=i,cnt=0,pos=1;
            while(j&&pos<=n/2){
                if (j&1){
                    sum+=a[pos];
                    cnt++;
                }
                j>>= 1;
                pos++;
            }
            if (Abs(sum)<ans){
                ans=Abs(sum);
                len=cnt;
            }
            else if(Abs(sum)==ans){
                len=min(len, cnt);
            }

            if(g[sum])
                g[sum]=min(g[sum], cnt);
            else
                g[sum]=cnt;
        }

        for (int i=1;i<(1<<(n-n/2));++i){
            ll sum=0;
            int cnt=0,pos=1,j=i;
            while (j&&pos+n/2<= n){
                if (j&1){
                    sum+= a[pos+n/2];
                    cnt++;
                }
                j>>= 1;
                pos++;
            }
            
            if(Abs(sum) < ans){
                ans=Abs(sum);
                len=cnt;
            }
            else if(Abs(sum)==ans){
                len=min(len,cnt);
            }

            map<ll,int>::iterator it=g.lower_bound(-sum);//迭代器都要忘光了orz

            if(it!=g.end()){
                if(Abs(sum+it->first)<ans){
                    ans=Abs(sum+it->first);
                    len=cnt+it->second;
                }
                else if(Abs(sum+it->first)==ans){
                    len=min(len,cnt+it->second);
                }
            }

            if (it!=g.begin()){
                it--;
                if(Abs(sum+it->first)<ans){
                    ans=Abs(sum+it->first);
                    len=cnt+it->second;
                }
                else if(Abs(sum+it->first)==ans){
                    len=min(len,cnt+it->second);
                }
            }
        }

        scanf("%d %d\n",Abs(ans),len);
    }
    return 0;
}

代码启发

https://www.jianshu.com/p/27eefa7b990e
膜一下dalao的玛丽

posted @ 2020-04-25 21:56  Midoria7  阅读(85)  评论(0编辑  收藏  举报