01分數規划是這樣的一類問題,有一堆物品,每一個物品有一個收益ai,一個代價bi,我們要求一個方案使選擇的$\sum{a_i}/\sum{b_i}$最大。
首先我們來一道例題吧,01分數規划的大體方法都是一樣的。
例1 Dropping Tests poj2976
給出n個物品,每個物品有兩個屬性a和b,選擇n-k個元素,詢問$\sum{a_i}/\sum{b_i}$的最大值。
1<=n<=1000,0<=k<n,0<=ai<=bi<=1000000000。
首先這題顯然是茲磁二分的,而$\sum{a_i}/\sum{b_i} \geq x$就等價於$\sum{a_i}-x\sum{b_i} \geq 0$。
所以我們發現二分完把ai-x*bi排序后把最大的n-k個選出來就行了。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <math.h> #include <limits> #include <set> #include <map>
using namespace std; int n,k,a[2333],b[2333]; double ps[2333]; bool ok(double x) { for(int i=1;i<=n;i++) ps[i]=a[i]-x*b[i]; sort(ps+1,ps+1+n); double ans=0; for(int i=n;i>=k+1;i--) ans+=ps[i]; return ans>=0; } void sol() { for(int i=1;i<=n;i++) scanf("%d",a+i); for(int i=1;i<=n;i++) scanf("%d",b+i); double l=0,r=1; while(r-l>1e-6) { double mid=(l+r)/2; if(ok(mid)) l=mid; else r=mid; } printf("%.0lf\n",l*100); } int main() { while(scanf("%d%d",&n,&k),n|k) sol(); }
但是還有一種神奇的做法,既然我們可以二分一個值,而且得出一個更優的解,那么我們就可以在這個解的基礎上繼續做,直到滿足精度。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <math.h> #include <limits> #include <set> #include <map> using namespace std; int n,k; struct pro {double a,b,p;}s[233333]; bool operator < (pro a,pro b) {return a.p<b.p;} double sol(double x) { for(int i=1;i<=n;i++) s[i].p=s[i].a-x*s[i].b; sort(s+1,s+1+n); double aa=0,bb=0; for(int i=n;i>=k+1;i--) aa+=s[i].a,bb+=s[i].b; return aa/bb; } void sol() { for(int i=1;i<=n;i++) scanf("%lf",&s[i].a); for(int i=1;i<=n;i++) scanf("%lf",&s[i].b); double l=0,ans=0; do { l=ans; ans=sol(ans); }while(fabs(l-ans)>1e-6); if(l>1) l=1; if(l<0) l=0; printf("%.0lf\n",l*100); } int main() { while(scanf("%d%d",&n,&k),n|k) sol(); }
例2 Desert King poj2728
(模型有轉化)
給出一個n個點的完全圖,每條邊有兩個權值cost和len。
求一個生成樹使$\frac{\sum{cost_i}}{\sum{len_i}}$最小。
2<=n<=1000
還是二分答案x,把每條邊邊權設為cost-x*len,跑最小生成樹,判答案的正負。建議用prim跑,復雜度比較科學。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <math.h> #include <limits> #include <set> #include <map> using namespace std; typedef long double ld; #define SZ 1066 int n,xx[SZ],yy[SZ],hh[SZ],fm[SZ]; bool del[SZ]; ld mind[SZ],d[SZ][SZ],d1[SZ][SZ],d2[SZ][SZ]; ld sqr(ld x) {return x*x;} ld Abs(ld x) {return (x>=0)?x:-x;} bool ok(ld x) { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) d[i][j]=d1[i][j]-x*d2[i][j]; } for(int i=0;i<=n;i++) mind[i]=1000000000, del[i]=0; mind[1]=0; fm[1]=0; ld ans=0; for(int i=1;i<=n;i++) { int mp=0; for(int j=1;j<=n;j++) { if(mind[j]<mind[mp]&&!del[j]) mp=j; } ans+=d[fm[mp]][mp]; del[mp]=1; for(int j=1;j<=n;j++) { if(d[mp][j]>=mind[j]||del[j]) continue; mind[j]=d[mp][j]; fm[j]=mp; } } return ans<=0; } void sol() { for(int i=1;i<=n;i++) scanf("%d%d%d",xx+i,yy+i,hh+i); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { d1[i][j]=Abs(hh[i]-hh[j]); d2[i][j]=sqrt(sqr(xx[i]-xx[j])+sqr(yy[i]-yy[j])); } } ld l=0,r=1000000000; while(r-l>1e-6) { double mid=(l+r)/2; if(ok(mid)) r=mid; else l=mid; } printf("%.3lf\n",(double)l); } int main() { while(scanf("%d",&n),n) sol(); }
直接迭代
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <math.h> #include <limits> #include <set> #include <map> using namespace std; #define SZ 1066 int n,xx[SZ],yy[SZ],hh[SZ],fm[SZ]; bool del[SZ]; double mind[SZ],d[SZ][SZ],d1[SZ][SZ],d2[SZ][SZ]; double sqr(double x) {return x*x;} double Abs(double x) {return (x>=0)?x:-x;} double sol(double x) { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) d[i][j]=d1[i][j]-x*d2[i][j]; } for(int i=0;i<=n;i++) mind[i]=1000000000, del[i]=0; mind[1]=0; fm[1]=0; double aa=0,bb=0; for(int i=1;i<=n;i++) { int mp=0; for(int j=1;j<=n;j++) { if(mind[j]<mind[mp]&&!del[j]) mp=j; } aa+=d1[fm[mp]][mp]; bb+=d2[fm[mp]][mp]; del[mp]=1; for(int j=1;j<=n;j++) { if(d[mp][j]>=mind[j]||del[j]) continue; mind[j]=d[mp][j]; fm[j]=mp; } } return aa/bb; } void sol() { for(int i=1;i<=n;i++) scanf("%d%d%d",xx+i,yy+i,hh+i); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { d1[i][j]=Abs(hh[i]-hh[j]); d2[i][j]=sqrt(sqr(xx[i]-xx[j])+sqr(yy[i]-yy[j])); } } double l,ans=0; do{ans=sol(l=ans);}while(fabs(l-ans)>1e-6); printf("%.3lf\n",l); } int main() { while(scanf("%d",&n),n) sol(); }
例3 Sightseeing Cows poj3621
給出一個有L個點P條邊的有向圖,每個點上有點權F,每條邊上有邊權T。
求一個回路使$\frac{\sum{F_i}}{\sum{T_i}}$最大。
2<=L<=1000,2<=P<=5000。
因為環上邊和點數相等,我們可以直接把邊權當做花費,入點的點權當作收入。
然后還是二分答案建圖,判圖中有沒有正環。取個反就成了判負環。
dfs版spfa即可。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <math.h> #include <limits> #include <set> #include <map> using namespace std; #define SZ 666666 int n,m,v[SZ],fst[SZ],vb[SZ],nxt[SZ],vc[SZ],M=0,X; bool vis[SZ],fh=0; double dist[SZ]; void adde(int a,int b,int c) {++M; nxt[M]=fst[a]; fst[a]=M; vb[M]=b; vc[M]=c;} void dfs(int p,double x) { vis[p]=1; for(int e=fst[p];e;e=nxt[e]) { double C=v[vb[e]]-x*vc[e]; if(dist[vb[e]]>=C+dist[p]) continue; if(vis[vb[e]]) {fh=1; return;} dist[vb[e]]=C+dist[p]; dfs(vb[e],x); if(fh) return; } vis[p]=0; } bool ok(double x) { for(int i=1;i<=n;i++) vis[i]=dist[i]=0; fh=0; for(int i=1;i<=n;i++) { dfs(i,x); if(fh) return 1; } return 0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",v+i); for(int i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); adde(a,b,c); } double l=0,r=1000000000; while(r-l>1e-6) { double mid=(l+r)/2; if(ok(mid)) l=mid; else r=mid; } printf("%.2lf\n",l); }