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


    题目内容

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

    输入格式

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

    数据范围

    (nle 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
    ",Abs(ans),len);
        }
        return 0;
    }
    

    代码启发

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

  • 相关阅读:
    vi/vim 文字处理器常用命令
    图片在容器里水平垂直居中
    谁的属性值优先被访问
    创建对象和实例
    碎碎念css
    未整理js
    表格<table>
    盒子模型
    Css文件目录结构
    链接文字<a>保持原有的字体颜色
  • 原文地址:https://www.cnblogs.com/Midoria7/p/12775566.html
Copyright © 2020-2023  润新知