1. 程式人生 > >POJ3977:Subset——題解(三分+折半搜索)

POJ3977:Subset——題解(三分+折半搜索)

枚舉 cstring 不為 tdi bsp inline poj 折半 %d

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; }

POJ3977:Subset——題解(三分+折半搜索)