[POJ2976][POJ2728]01分数规划问题的二分答案解法
这里就不放原题目了。
POJ2976就是01分数规划的模板题,题目形式就是有n个物品,每个物品有对应的价值ai和代价bi,我们要取K个物品,使取的物品的 最小。
二分答案的解法特别妙,我们设 r= ,那么就有 由此不难发现,只要满足这条式子,我们能取的r越大越好。
不难发现此时已经满足二分答案的性质了。
二分r的大小,如果最后式子左边大于0,那么说明r取小了,如果左边小于0,说明r取大了。
那么我的代码如下:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int N,K; double a[2000],b[2000]; double de[2000]; int main() { while(1) { scanf("%d%d",&N,&K); if(N==0&&K==0) return 0; for(int i=1;i<=N;i++) scanf("%lf",&a[i]); for(int i=1;i<=N;i++) scanf("%lf",&b[i]); double l=0,r=1,mid; while(r-l>0.000006) { mid=(l+r)*1.0/2; for(int i=1;i<=N;i++) de[i]=a[i]-mid*b[i]; sort(de+1,de+N+1); long double sum=0; for(int i=K+1;i<=N;i++) sum+=de[i]; if(sum>0) l=mid; else r=mid; } printf("%.0f\n",mid*100); } }
而POJ2728就有点小意思了,它是有01划分性质的一个平面图最小生成树。准确来说,是取生成树的边第一权值的和比上边第二权值的和的最小值。
那么我们还是用上文所述的01分数规划来做,二分答案,然后把二分出来的r值带入图中,用prim去跑一遍。(机房有位大佬不肯放弃kruscal,在卡了一晚上常数之后说了句真香)
于是我的代码如下(prim未优化)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int N; struct node{ int x,y,z; friend double dis(node a,node b) { return sqrt(1.0*(a.x-b.x)*(a.x-b.x)+1.0*(a.y-b.y)*(a.y-b.y)); } }po[1005]; double edge[1005][1005],tot,high[1005],lowcost[1005]; int close[1005]; double prim(double k) { double cost=0,len=0; double sum=0; for(int i=1;i<=N;i++) { close[i]=1; lowcost[i]=abs(po[1].z-po[i].z)-edge[1][i]*k; } /*for(int i=1;i<=N;i++) cout<<lowcost[i]<<" "; cout<<endl;*/ close[1]=-1; for(int i=1;i<N;i++) { double minn=1000000000; int v=-1; for(int j=1;j<=N;j++) if(close[j]!=-1&&lowcost[j]<minn) { v=j; minn=lowcost[j]; } if(v!=-1) { cost+=abs(po[close[v]].z-po[v].z); len+=edge[close[v]][v]; close[v]=-1; sum+=lowcost[v]; for(int j=1;j<=N;j++) { double tmp=abs(po[v].z-po[j].z)-edge[v][j]*k; if(close[j]!=-1&&tmp<lowcost[j]) { lowcost[j]=tmp; close[j]=v; } } } } //cout<<k<<" "<<sum<<endl; return sum; } int main() { while(1) { scanf("%d",&N); if(N==0) return 0; for(int i=1;i<=N;i++) scanf("%d%d%d",&po[i].x,&po[i].y,&po[i].z); for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) edge[i][j]=dis(po[i],po[j]); double l=0.0,r=100.0,mid; while(r-l>1e-6) { mid=(l+r)/2; if(prim(mid)>=0) l=mid; else r=mid; } printf("%.3f\n",l); } }