• [POJ3977] Subset


    题目

    一句话题意:
    多组数据,以(0)为结尾,给你(n)个数,求出这(n)个数的一个非空子集,使子集中的数加和的绝对值最小,在此基础上子集中元素的个数应最小。(n<=35),需要(long long)

    解说

    大家的第一想法大约是枚举吧。巧了我也是。但是这样T没商量,时间复杂度(O(2^n * n)) (为什么是这个复杂度一会儿看下面代码中的枚举部分)。

    那我应该怎么办?卡了……

    看看题解吧……

    然后它告诉我……分两边枚举。

    蛤???那不还是枚举吗?????

    呵呵,人家的枚举不只是分两边,还有更高大上的做法。采用二分的思想,分成两个集合,这样每边最多(18)个元素,分别进行枚举,复杂度也就降下来了。

    然后枚举其中一个子集,排序后暂存后,再枚举另一个子集,通过二分查找与寻找合适的子集并与第一个集合的子集相加,从而找到绝对值最小的子集。

    就是这样。

    再一看代码,差点崩溃。原理看的明明白白,但是代码看一遍根本看不明白,各种神奇操作……我用了许久看懂了代码,呈现出这个下面这个自己加的满是注释的版本。

    代码

    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<map>
    #include<iostream>
    const int maxn = 1000+5;
    using namespace std;
    typedef long long ll; 
    ll a[maxn];
    ll Abs(ll x){
        return x<0?-x:x;
    }
    int main(){
        int n;
        while(scanf("%d",&n)!=EOF&&n) {
            for(int i=0;i<n;i++)
                scanf("%lld",&a[i]);
            int half=n/2;//划分两边 
            pair<ll,int> res(Abs(a[0]),1);//res记录最小值用 
            map<ll,int> mp;//mp的键-值对为和的大小-用了几个数 
            map<ll,int>::iterator it;//看见这个就知道高端操作开始了……
            for(int i=1;i<(1<<half);i++) {//枚举每个数选不选的情况 
                ll sum=0;
                int cnt=0;
                for(int j=0;j<half;j++){
                    if(i>>j&1){//判断一个数是否被选 
                        sum+=a[j];
                        cnt++;
                    }
                }
                pair<ll,int> temp(Abs(sum),cnt);//把绝对值和选的数字个数存进pair里 
                res=min(res,temp);//记录最小值 
                //这里的pair就用的很灵性
    			//pair比大小自动先比first,第一位相等再看第二位,很方便 
                if(mp[sum]) mp[sum]=min(mp[sum],cnt);
                else mp[sum]=cnt;//这两行储存和为sum时最少选几个数 
            }
            for(int i=1;i<1<<(n-half);i++){
    			//后半截操作和前半截大致相同 
                ll sum=0;
                int cnt=0;
                for(int j=0;j<n-half;j++) {
                    if(i>>j&1){
                        sum+=a[j+half];
                        cnt++;
                    }
     
                }
                pair<ll,int> temp(Abs(sum),cnt);
                res=min(res,temp);
                //分割线,下面开始和上面不一样 
                it=mp.lower_bound(-sum);//找到和sum最匹配的位置 
                //lower_bound配合map的高端操作,活久见 
                if(it!=mp.end()){
                    pair<ll,int> temp(Abs(sum+it->first),cnt+it->second);
                    res=min(res,temp);
                }
                if(it!=mp.begin()) {
                    it--;
                    pair<ll,int> temp(Abs(sum+it->first),cnt+it->second);
                    res=min(res,temp);
                }
            }
            printf("%lld %d
    ",res.first,res.second);
        }
        return 0;
    }
    

    幸甚至哉,歌以咏志。

  • 相关阅读:
    欢乐送小程序自动化探索实践
    看完这篇还不了解 Nginx,那我就哭了!
    测试人的技术栈
    Bug,项目过程中的重要数据
    什么是测试开发工程师?
    hdu 1219 AC Me
    hdu 1202 The calculation of GPA(算绩点问题)
    hdu1205吃糖果(插空法)
    hdu1201(18岁生日)
    hdu1231最大连续子序列
  • 原文地址:https://www.cnblogs.com/DarthVictor/p/12775664.html
Copyright © 2020-2023  润新知