01分数规划入门
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); }