POJ3977:Subset——题解(三分+折半搜索)
http://poj.org/problem?id=3977
题目大意:有一堆数,取出一些数,记他们和的绝对值为w,取的个数为n,求在w最小的情况下,n最小,并输出w,n。
————————————————————
两天时间,终于搞下。
这题显然我们唯一能做到的只有暴力,但是2^35显然不可取……
但是显然我们折半搜索的话复杂度只有2^18左右所以没问题。
将数分成两堆,每一堆暴力求出所有情况并记录。
然后枚举第一堆,三分第二堆求解即可。
(为什么三分呢?因为绝对值啊,一定最优解是在函数的最低点,所以是单峰函数)
……思路挺简单是不是,但是注意以下几点:
1.n不为零,这点需要特判。
2.三分很容易写跪,具体怎么做看我代码。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const ll INF=8223372036854775807; inline ll abss(ll a){ if(a<0)return -a; return a; } struct num{ ll w; ll n; }mp1[300001],mp2[300001]; ll a[36]; int cnt1=0,cnt2=0;; bool in[36]; void dfs(int n,int k,bool t){ memset(in,0,sizeof(in)); if(!t){ mp1[++cnt1].w=mp1[cnt1].n=0; } for(int res=1;res<=((1<<k)-1);res++){ int cnt=0; int lz=res; while(lz){ in[++cnt]=lz-lz/2*2; lz/=2; } ll sum=0,num=0; for(int i=1;i<=cnt;i++){ int j=i; if(t)j+=n/2; if(in[i]){ sum+=a[j]; num++; } } if(!t){ mp1[++cnt1].w=sum; mp1[cnt1].n=num; }else{ mp2[++cnt2].w=sum; mp2[cnt2].n=num; } } return; } bool cmp(num c,num d){ if(c.w<d.w)return 1; if(c.w>d.w)return 0; if(c.n<d.n)return 1; return 0; } ll ans,cnt; void sanfen(int l,int r,num k){ if(r-l<=2){ ll t1=abss(k.w+mp2[l].w); ll t2=abss(k.w+mp2[r].w); ll t3=abss(k.w+mp2[(l+r)/2].w); ll c1=mp2[l].n+k.n; ll c2=mp2[r].n+k.n; ll c3=mp2[(l+r)/2].n+k.n; ll t,c; if(t1>t2||(t1==t2&&c1>c2)){ t=t2;c=c2; }else{ t=t1;c=c1; } if(t>t3||(t==t3&&c>c3)){ t=t3;c=c3; } if(ans>t||(ans==t&&cnt>c)){ ans=t;cnt=c; } return; } int mid1=(r+2*l)/3; int mid2=(l+2*r)/3; ll t1=abss(k.w+mp2[mid1].w); ll t2=abss(k.w+mp2[mid2].w); if(t1<t2||(t1==t2&&mp2[mid1].n+k.n<mp2[mid2].n+k.n)){ sanfen(l,mid2-1,k); }else{ sanfen(mid1+1,r,k); } return; } int main(){ int n; while(scanf("%d",&n)!=EOF&&n){ cnt1=0,cnt2=0; for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } dfs(n,n/2,0); dfs(n,n-n/2,1); sort(mp2+1,mp2+cnt2+1,cmp); //for(int i=1;i<=cnt1;i++)printf("1 %lld %lld\n",mp1[i].w,mp1[i].n); //for(int i=1;i<=cnt2;i++)printf("2 %lld %lld\n",mp2[i].w,mp2[i].n); ans=INF;cnt=INF; for(int i=1;i<=cnt1;i++){ sanfen(1,cnt2,mp1[i]); if(i>1){ if(ans>abss(mp1[i].w)||(ans==abss(mp1[i].w)&&cnt>mp1[i].n)){ ans=abss(mp1[i].w);cnt=mp1[i].n; } } } printf("%lld %lld\n",ans,cnt); } return 0; }