[POJ3977] Subset

題目

一句話題意:
多組數據,以\(0\)爲結尾,給你\(n\)個數,求出這\(n\)個數的一個非空子集,使子集中的數加和的絕對值最小,在此基礎上子集中元素的個數應最小。\(n<=35\),須要\(long long\)ios

解說

你們的第一想法大約是枚舉吧。巧了我也是。可是這樣T沒商量,時間複雜度\(O(2^n * n)\) (爲何是這個複雜度一下子看下面代碼中的枚舉部分)。spa

那我應該怎麼辦?卡了……code

看看題解吧……排序

而後它告訴我……分兩邊枚舉。string

蛤???那不仍是枚舉嗎?????it

呵呵,人家的枚舉不僅是分兩邊,還有更高大上的作法。採用二分的思想,分紅兩個集合,這樣每邊最多\(18\)個元素,分別進行枚舉,複雜度也就降下來了。io

而後枚舉其中一個子集,排序後暫存後,再枚舉另外一個子集,經過二分查找與尋找合適的子集並與第一個集合的子集相加,從而找到絕對值最小的子集。class

就是這樣。stream

再一看代碼,差點崩潰。原理看的明明白白,可是代碼看一遍根本看不明白,各類神奇操做……我用了許久看懂了代碼,呈現出這個下面這個本身加的盡是註釋的版本。基礎

代碼

#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\n",res.first,res.second);
    }
    return 0;
}

幸甚至哉,歌以詠志。

相關文章
相關標籤/搜索