• 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}-xsum{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
    ",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
    ",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
    ",(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
    ",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
    ",l);
    }
    迭代的话…似乎不太好写,就算了吧
  • 相关阅读:
    2-Fourteenth Scrum Meeting-20151214
    2-Thirteenth Scrum Meeting-10151213
    2-Twelfth Scrum Meeting20151212
    2-Eleventh Scrum Meeting20151211
    L465 Climate Change May Take Away Your Ability to Flush the Toilet
    L458 Interview
    L457 早上锻炼减肥
    L456 学英语 看美剧
    L455 人之初,性本衰
    L454 正确喝水
  • 原文地址:https://www.cnblogs.com/zzqsblog/p/5450361.html
Copyright © 2020-2023  润新知