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}-x\sum{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\n",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\n",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\n",(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\n",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\n",l);
}
迭代的話…似乎不太好寫,就算了吧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM