编程之美第一篇 01分数规划
欢迎大家访问handsomecui的博客,转载请注明地址https://www.cnblogs.com/handsomecui,欢迎加入技术群讨论:778757421
正文
01分数规划
01分数规划问题其实就是解决单价之类的问题,假设给你n个物品,让你找出选k个物品的最大单价;例如南阳oj:Yougth的最大化;解决这类问题可以用二分查找,这类问题跟二分极大化最小值,极小化最大值有一些相似的地方,均是从结果出发,来进行二分查找;例如上面南阳那道题,可以转化一下;
由于v/w=单价;所以v=w*单价;即v-w*单价=0;有了这个关系,我们马上可以想到二分来查找这个值;
那么我们可以定义一个count数组来记录v-w*单价的值;由于选k个只需要把count从大到小排下序就可以了;然后就是二分了;这类问题就是01分数规划问题;
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include<stdio.h> #include<algorithm> #define MAX(x,y) x>y?x:y using namespace std; const int MAXN=10010; struct Node{ int v,w; }; Node res[MAXN]; double cont[MAXN]; int n,k; int fun( double mid){<br> for ( int i=0;i<n;i++){ cont[i]=res[i].v-mid*res[i].w; } sort(cont,cont+n); double sum=0; for ( int i=n-1;i>=n-k;i--)sum+=cont[i];<br> //printf("%lf\n",mid); return sum>=0?1:0; } double abs( double x){ return x>0?x:-x; } double search( double max){<br> double left=0,right=max,mid; while (right-left>1e-10){mid=(left+right)/2; if (fun(mid))left=mid; else right=mid; } return mid; } int main(){ while (~scanf( "%d%d" ,&n,&k)){ double max=0; for ( int i=0;i<n;i++){ scanf( "%d%d" ,&res[i].w,&res[i].v); max=MAX(max,res[i].v*1.0/res[i].w); //<br> } printf( "%.2f\n" ,search(max)); } return 0; } |
与这个题相似的还有poj Dropping tests;这个题意是:
给你n个数,让求删除k个数后
的最大值;
跟南阳那个题很相似,只需要找n-k个数即可;
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include<cstdio> #include<iostream> #include<algorithm> #include<cstring> #include<cmath> using namespace std; const int MAXN=10010; struct Node{ int a,b; }; Node dt[MAXN]; double d[MAXN]; int n,k; bool fsgh( double R){ double sum=0; for ( int i=0;i<n;i++)d[i]=dt[i].a-R*dt[i].b; sort(d,d+n); for ( int i=n-1;i>=n-k;i--)sum+=d[i]; return sum>0? true : false ; } double erfen( double l, double r){ double mid; while (r-l>1e-6){ mid=(l+r)/2; if (fsgh(mid))l=mid; else r=mid; } return mid; } int main(){ while (scanf( "%d%d" ,&n,&k),n|k){ double mx=0; k=n-k; for ( int i=0;i<n;i++)scanf( "%d" ,&dt[i].a); for ( int i=0;i<n;i++)scanf( "%d" ,&dt[i].b),mx=max(1.0*dt[i].a/dt[i].b,mx); printf( "%.0f\n" ,erfen(0,mx)*100); } return 0; } |
另外01分数规划还可以与图论结合在一起;例如:
和最小生成树结合在一起让你求一棵最优比率生成树;例如 poj Desert King;
题意:有N个村庄,给出每个村庄的坐标和海拔,,benifit为两点之间的距离,cost为两点的高度差,现在要求一棵树使得 cost / benift 最小;
咋一看跟01分数规划还真像,但是让求的是一棵树,我们该怎么办?其实就是求一个最小生成树罢了,最小生成树里面的low数组代表的是啥?权值!那直接把low数组里面的值换成cost-benift*R就好了,然后就是一个二分问题了;
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | #include<cstdio> #include<iostream> #include<cstring> #include<cmath> #include<algorithm> #define mem(x,y) memset(x,y,sizeof(x)) using namespace std; const int INF=0x3f3f3f3f; typedef long long LL; const int MAXN=1010; double vis[MAXN],low[MAXN]; int N; double R; struct Node{ double x,y,h; }; Node dt[MAXN]; double len[MAXN][MAXN],cost[MAXN][MAXN]; double getl(Node a,Node b){ double x=b.x-a.x,y=b.y-a.y; return sqrt(x*x+y*y); } bool prime(){ double total; mem(vis,0); for ( int i=0;i<N;i++)low[i]=cost[0][i]-R*len[0][i]; total=0; vis[0]=1; //<br> for(int i=0;i<N;i++){ double temp=INF; int k; for ( int j=0;j<N;j++) if (!vis[j]&&low[j]<temp)temp=low[j],k=j; if (temp==INF) break ; total+=temp; vis[k]=1; for ( int j=0;j<N;j++) if (!vis[j]&&low[j]>cost[k][j]-R*len[k][j])low[j]=cost[k][j]-R*len[k][j]; } //printf("total=%lf R=%lf\n",total,R); if (total>0) return true ; else return false ; } int main(){ while (scanf( "%d" ,&N),N){ mem(len,INF); mem(cost,INF); double mxl=-INF,mil=INF,mxc=-INF,mic=INF; for ( int i=0;i<N;i++) scanf( "%lf%lf%lf" ,&dt[i].x,&dt[i].y,&dt[i].h); for ( int i=0;i<N;i++){ for ( int j=i+1;j<N;j++){ len[j][i]=len[i][j]=getl(dt[i],dt[j]); cost[j][i]=cost[i][j]=abs(dt[i].h-dt[j].h); mxl=max(mxl,len[i][j]); mxc=max(mxc,cost[i][j]); mil=min(mil,len[i][j]); mic=min(mic,cost[i][j]); } } //printf("%lf %lf %lf %lf\n",mil,mic,mxl,mxc); double l=mic/mxl,r=mxc/mil; // printf("%lf %lf\n",l,r); while (r-l>1e-4){ R=(l+r)/2; if (prime())l=R; else r=R; } printf( "%.3f\n" ,l); } return 0; } |
另外01分数规划还可以与最短路结合在一起;求一个最优比率环;例如poj Sightseeing Cows;
题意:这个人带牛旅行,旅行每个城市会有幸福度,通过每个城市会花费时间,让找平均每秒的最大幸福度;
注意这题是让求最大的,好像我们前面讲的都是最小的,那该怎么办呐?很简单以前是hp-R*t;转成R*t-hp不就好了吗?照样转化成最短路问题;但是可能产生负边啊;对了就是负边,二分就是从负边出发的,有负边证明t太大,没有t小,这就是二分的一个条件;由于负边我们可以用bellman,或者邻接表解决;
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | #include<cstdio> #include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<queue> #define mem(x,y) memset(x,y,sizeof(x)) using namespace std; const int INF=10000000000000; typedef long long LL; const int MAXN=1010; const int MAXM=100010; /*struct Node{ int u,v; double t; }; Node dt[MAXM];*/ struct Edge{ int from ,to,next,t; }; Edge edg[MAXM]; int head[MAXM]; int edgnum; void add( int u, int v, int t){ Edge E={u,v,head[u],t}; edg[edgnum]=E; head[u]=edgnum++; } int L,P; double hp[MAXN],dis[MAXN]; int usd[MAXN],vis[MAXN]; /*void add(int u,int v,double t){ Node E={u,v,t}; dt[edgnum++]=E; }*/ //double R; /*bool Bellman(){ mem(dis,INF); mem(usd,0); dis[1]=0; while(1){ int temp=0; for(int j=0;j<edgnum;j++){ int u=dt[j].u,v=dt[j].v; double t=dt[j].t; //dis[v]=min(dis[v],dis[u]+R*t-hp[u]);//应该是R*t-hp[u]; if(dis[v]>dis[u]+R*t-hp[u])usd[v]++,dis[v]=dis[u]+R*t-hp[u],temp=1; if(usd[v]>L)return false; } if(!temp)return true; } }*/ bool SPFA( double R){ queue< int >dl; while (!dl.empty())dl.pop(); for ( int i=1;i<=L;i++){ dis[i]=INF; vis[i]=0; usd[i]=0; } dl.push(1); vis[1]=1; usd[1]++; dis[1]=0; while (!dl.empty()){ int u=dl.front(); dl.pop(); vis[u]=0; for ( int i=head[u];i!=-1;i=edg[i].next){ int v=edg[i].to,t=edg[i].t; if (dis[v]>dis[u]+R*t-hp[u]){ dis[v]=dis[u]+R*t-hp[u]; if (!vis[v]){ vis[v]=1; usd[v]++; dl.push(v); // printf("%d\n",usd[v]); if (usd[v]>=L) return false ; } } } } return true ; } int main(){ int a,b; int c; while (~scanf( "%d%d" ,&L,&P)){ edgnum=0; double mih=INF,mxh=-INF; int mit=INF,mxt=-INF; mem(head,-1); for ( int i=1;i<=L;i++){ scanf( "%lf" ,hp+i); mih=min(mih,hp[i]); mxh=max(mxh,hp[i]); } while (P--){ scanf( "%d%d%d" ,&a,&b,&c); add(a,b,c); mit=min(mit,c); mxt=max(mxt,c); } double l=mih/mxt,r=mxh/mit; // printf("%f %f\n",l,r); double R; while (r-l>=0.001){ R=(l+r)/2; if (SPFA(R))r=R; else l=R; } printf( "%.2f\n" ,l); } return 0; } |
01分数规划是一个基本而且有用的算法,可以与图论连用,也可以与数据结构一起用;
上面提到的题解详情请见:
http://www.cnblogs.com/handsomecui/p/4690691.html
http://www.cnblogs.com/handsomecui/p/4971467.html
http://www.cnblogs.com/handsomecui/p/4972701.html
http://www.cnblogs.com/handsomecui/p/4973041.html
转载请附上地址:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架