一句话题意:
多组数据,以\(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; }
幸甚至哉,歌以咏志。