• 编程之美第一篇 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 ",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 ",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
    ",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 ",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 ",mil,mic,mxl,mxc); double l=mic/mxl,r=mxc/mil; // printf("%lf %lf ",l,r); while(r-l>1e-4){ R=(l+r)/2; if(prime())l=R; else r=R; } printf("%.3f ",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
    ",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
    ",l,r);
            double R;
            while(r-l>=0.001){
                R=(l+r)/2;
                if(SPFA(R))r=R;
                else l=R;
            }
            printf("%.2f
    ",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

  • 相关阅读:
    ASP.NET MVC中 CKeditor 通过两种方法向后台传值以及编码、乱码问题
    如何解析<textarea>标签中的html代码
    ASP.NET MVC中,后台向前台传递多个对象(表)的方法
    ASP.NET MVC 环境下CKeditor 的配置以及用jQuery进行数据存取操作
    jquery下 动态显示jqGrid 以及jqGrid的属性设置容易出现的问题
    ASP.NET MVC 中 CKeditor.js的正确引用
    关于有道词典的一个小错误
    ASP.NET MVC 标签绑定传值及后台获取并进行修改操作
    每天学点GDB 6
    每天学点GDB 9
  • 原文地址:https://www.cnblogs.com/handsomecui/p/5116886.html
Copyright © 2020-2023  润新知