POJ 3977 折半枚举

题目链接:http://poj.org/problem?id=3977

前言

如无法区分折半枚举,二分,请点击这里

分析

这题我感觉出了是用枚举,毕竟数据范围很小,但是,集合中每个元素都有可能被选或者不被选,根据计数原理应该会有\(2^{35}-1\)种情况,需要刨除空集,枚举显然是会T掉,那怎么办呢?
考虑选出来的集合有几种情况,有可能都在前一半,有可能都在后一半,还有可能前后都有,前后都有的情况可以拆分成前两种情况, 所以优先考虑前两种,35的一半可以取17,发现\(2^{17}\)是不会T的,我们只要每次枚举一半,然后就能解决这个问题,这就是如题所说的折半枚举。
使用上述方法可以解决前两种情况,那前后都有呢?前后都有的必然是由前一半的一个子集和后一半的一个子集构成,因为要绝对值最小,所以要使这个集合接近于0,因此枚举前一个集合的每个子集,然后找到一个子集的和与这个子集的相反数最接近的集合,把他们当作答案就行。
这道题来说的话,感觉如果不知道折半枚举这种思想,做起来还是挺困难的 ,有的时候暴力并不是没什么用,毕竟人家的另一个名字叫朴素算法,也许有的时候想不出什么高端数据结构,什么转移方程,不如写个暴力看看,也许优化一下就能A,就算A不了也会有很多过程分吧,毕竟OI不像ACM那样只算AC的题目,也许10分就能拉开很大差距。

#include<cstdio> 
#include<cstring> 
#include<algorithm> 
#include<map> 
#define ll long long 
const int N=40; 
using namespace std; 
ll a[N],n; 
pair<ll,ll> ans; 
map<ll,ll> p; 
map<ll,ll>::iterator it; 
ll abs(ll x){ 
    return x>0?x:-x; 
} 
void calc(){ 
    for(ll i=1;i<(1<<n/2);i++){ 
        ll t=i,sum=0,len=0; 
        for(ll j=n/2-1;j>=0;j--) 
            if(t&(1<<j))sum+=a[j],len++; 
        ll tep=abs(sum); 
        if(tep<ans.first || (tep==ans.first&&len<ans.second)) ans=make_pair(tep,len); 
        if(p[sum]>0)p[sum]=min(p[sum],len); 
        else p[sum]=len; 
    } 
    for(ll i=1;i<(1<<(n-n/2));i++){ 
        ll t=i,sum=0,len=0; 
        for(ll j=n-1;j>=n/2;j--){ 
            ll v=j-n/2; 
            if(t&(1<<v))sum+=a[j],len++; 
        } 
        ll tep=abs(sum); 
        if(tep<ans.first||(tep==ans.first&&len<ans.second)) 
            ans=make_pair(tep,len); 
        it=p.lower_bound(-sum); 
        if(it!=p.end()){ 
            ll val=abs((*it).first+sum),l=(*it).second+len;     
            if(ans.first>val||(ans.first==val&&l<ans.second)) 
            ans=make_pair(val,l); 
        } 
        if(it!=p.begin()){ 
            it--; 
            ll val=abs((*it).first+sum),l=(*it).second+len; 
            if(ans.first>val||(ans.first==val&&l<ans.second)) ans=make_pair(val,l);
        } 
    } 
    printf("%lld %lld\n",ans.first,ans.second); 
} 
int main(){ 
    while(~scanf("%lld",&n)){ 
        if(n==0)return 0; 
        p.clear(); 
        for(int i=0;i<n;i++) 
            scanf("%lld",&a[i]); 
        ans=make_pair(abs(a[0]),1); 
        calc(); 
    } 
}
posted @ 2020-04-26 13:21  An_Fly  阅读(137)  评论(0编辑  收藏  举报