编程之美第一篇 01分数规划

01分数规划

01分数规划问题其实就是解决单价之类的问题,假设给你n个物品,让你找出选k个物品的最大单价;例如南阳oj:Yougth的最大化;解决这类问题可以用二分查找,这类问题跟二分极大化最小值,极小化最大值有一些相似的地方,均是从结果出发,来进行二分查找;例如上面南阳那道题,可以转化一下;

由于v/w=单价;所以v=w*单价;即v-w*单价=0;有了这个关系,我们马上可以想到二分来查找这个值;

那么我们可以定义一个count数组来记录v-w*单价的值;由于选k个只需要把count从大到小排下序就可以了;然后就是二分了;这类问题就是01分数规划问题;

代码实现:

#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){    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];    //printf("%lf\n",mid);
    return sum>=0?1:0;
}
double abs(double x){
    return x>0?x:-x;
}
double search(double max){    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);//        }
        printf("%.2f\n",search(max));
    }
    return 0;
}

  与这个题相似的还有poj Dropping tests;这个题意是:

给你n个数,让求删除k个数后

的最大值;

跟南阳那个题很相似,只需要找n-k个数即可;

代码实现:

#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就好了,然后就是一个二分问题了;

代码实现:

#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;//    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,或者邻接表解决;

代码实现:

#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

转载请附上地址:

http://www.cnblogs.com/handsomecui

时间: 2024-08-02 06:59:03

编程之美第一篇 01分数规划的相关文章

01分数规划(转)

01分数规划 分类: DP&&记忆化搜索2013-05-04 14:47 4193人阅读 评论(1) 收藏 举报 [关键字] 0/1分数规划.最优比率生成树.最优比率环 [背景] 根据楼教主的回忆录,他曾经在某一场比赛中秒掉了一道最优比率生成树问题,导致很多人跟风失败,最终悲剧.可见最优比率生成树是多么凶残的东西,但是这个东西只要好好研究半天就可以掌握,相信你在看了我写的这篇总结之后可以像楼教主一般秒掉这类问题. 因为网上对于01分数规划问题的详细资料并不是太多,所以我就结合自己的一些理解

【转】[Algorithm]01分数规划

因为搜索关于CFRound277.5E题的题解时发现了这篇文章,很多地方都有值得借鉴的东西,因此转了过来 原文:http://www.cnblogs.com/perseawe/archive/2012/05/03/01fsgh.html [关键字] 0/1分数规划.最优比率生成树.最优比率环 [背景] 根据楼教主的回忆录,他曾经在某一场比赛中秒掉了一道最优比率生成树问题,导致很多人跟风失败,最终悲剧. 自己总结了一些这种问题的解法,因为水平有限,如果有错误或是麻烦的地方,尽管喷,邮箱或是下方留言

POJ 2728 Desert King (01分数规划)

Desert King Time Limit: 3000MS   Memory Limit: 65536K Total Submissions:29775   Accepted: 8192 Description David the Great has just become the king of a desert country. To win the respect of his people, he decided to build channels all over his count

[POJ2976][POJ2728]01分数规划问题的二分答案解法

这里就不放原题目了. POJ2976就是01分数规划的模板题,题目形式就是有n个物品,每个物品有对应的价值ai和代价bi,我们要取K个物品,使取的物品的  最小. 二分答案的解法特别妙,我们设 r= ,那么就有   由此不难发现,只要满足这条式子,我们能取的r越大越好. 不难发现此时已经满足二分答案的性质了. 二分r的大小,如果最后式子左边大于0,那么说明r取小了,如果左边小于0,说明r取大了. 那么我的代码如下: #include<iostream> #include<cstdio&g

[转]01分数规划算法 ACM 二分 Dinkelbach 最优比率生成树 最优比率环

01分数规划 前置技能 二分思想最短路算法一些数学脑细胞?问题模型1 基本01分数规划问题 给定nn个二元组(valuei,costi)(valuei,costi),valueivaluei是选择此二元组获得的价值(非负),costicosti是选择此二元组付出的代价(非负),设xi(xi∈{0,1})xi(xi∈{0,1})代表第ii个二元组的选与不选,最大(小)化下式 maximize(or minimize)   r=∑valuei?xi∑costi?ximaximize(or minim

01分数规划模板

/* 01分数规划模板(Dinkelbach) 01分数规划就是把 sum(a)/sum(b)转换成 f(x)=sum(a)-ans*sum(b); 当f(x)取到0时,ans取到了最大(小)值 poj 2976 两个长度为n的数组a,b 可以除去m个,怎样选择才能使剩下的 sum(a)/sum(b)的百分数最大 */ int n,m; struct Node{ int a,b; double v; ///用于排序筛选较大的值(题目要求极大值 bool operator<(const Node&am

poj3621 Sightseeing Cows --- 01分数规划

典型的求最优比例环问题 参考资料: http://blog.csdn.net/hhaile/article/details/8883652 此题中,给出每个点和每条边的权值,求一个环使 ans=∑点权/∑边权 最大. 因为题目要求一个环,而且必然是首尾相接的一个我们理解的纯粹的环,不可能是其他样子的环, 所以我们可以把一条边和指向的点看做整体处理. 上面方程可以化为:ans×e[i]-p[i]=0 以它为边权二分答案,spfa求负环,有负环则该ans可行,增大下界. 若一直不可行,则无解. #i

【Earthquake, 2001 Open 】 0-1 分数规划

71  奶牛施工队一场地震把约翰家园摧毁了,坚强的约翰决心重建家园.约翰已经修复了 N 个牧场,他需要再修复一些道路把它们连接起来.碰巧的是,奶牛们最近也成立了一个工程队,专门从事道路修复.而然,奶牛们很有经济头脑,如果无利可图,它们是不会干的.约翰和奶牛达成了协议,约翰向奶牛支付 F 元,奶牛负责修路,但不必修复所有道路,只要确保所有牧场连通即可.可供奶牛选择修复的道路有 M 条,第 i 条道路连接第 Ui 个牧场和第 Vi 个牧场,修复需要 Ti 分钟,支出成本为 Ci.保证连通所有牧场的道

zoj 2676 Network Wars(最小割,01分数规划)

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2676 大致题意:给出一个带权无向图,每条边有一个边权wi,求将S和T分开的一个割边集C,使得该割边集的平均边权最小,即最小化∑wi / |C| . 详见amber关于最小割模型的论文 思路:amber论文中详细讲解了如何转化成函数及建图,值得注意的是当边被重新赋权后,对于wi < 0 的边权,该边必然在最小割中,不必再建边,直接加入最大流中即可,因为求最小割时边权都为正值