[例题/总结]0/1分数规划


一、总述

0/1分数规划是专门解决0/1分数规划模型的一种算法(废话)。所以说0/1分数规划模型是什么呢?给定整数{\(a_1,a_2,a_3,...,a_n\)},{\(b_1,b_2,b_3,...,b_n\)}从中选出若干对数,使得它们各自和的比值最大。公式如下:
\[\frac{\sum_{p=1}^{n}a_p\times x_p}{\sum_{p=1}^{n}b_p\times x_p}(x_p=1,0)\]

二、实现原理

那么我们用什么方法可以求出这样一个看上去十分复杂的柿子呢?正确的答案是二分法,但是目前来看求解该式的最大值与二分法无关。
我们可以任意猜测一个比值\(Q\),此时分为两种情况讨论:

  1. \(\exists\){\(x_1,x_2,...,x_n\)},使得\(\frac{\sum_{p=1}^{n}a_p\times x_p}{\sum_{p=1}^{n}b_p\times x_p}(x_p=1,0)\geqslant Q\)
    通过计算发现存在解使得答案大于\(Q\),这说明此时我们的\(Q\)猜小了,\(Q\)还可以继续变大。

  2. \(\forall\){\(x_1,x_2,...,x_n\)},使得\(\frac{\sum_{p=1}^{n}a_p\times x_p}{\sum_{p=1}^{n}b_p\times x_p}(x_p=1,0)\lt Q\)
    所有的答案都比\(Q\)小,可以得出我们的\(Q\)枚举的大了,需要减少。

这个求解\(Q\)的过程是不是很熟悉?机智的你一定发现\(Q\)具有二分性,这和二分答案方法是一样的,至此我们可以用二分答案解决这个问题。

那么如何计算是否存在这样的比值大于我们枚举的\(Q\)呢?显然,直接计算这个比值是极其不明智的选择。这时需要我们对公式进行一个小小的变形。
观察这个式子:
\[\frac{\sum_{p=1}^{n}a_p\times x_p}{\sum_{p=1}^{n}b_p\times x_p}(x_p=1,0)\geqslant Q\]
我们把分母乘到等式右边:
\[\sum_{p=1}^{n}a_p\times x_p\geqslant \sum_{p=1}^{n}Q\times b_p\times x_p\]
在把右边的柿子移到左边来:
\[\sum_{p=1}^{n}a_p\times x_p - \sum_{p=1}^{n}Q\times b_p\times x_p\geqslant 0\]
提取公因式得到最终公式:
\[\sum_{p=1}^{n}(a_p-Q\times b_p)\times x_p\geqslant 0\]

现在我们知道怎么做了,由于只要存在一组解就可以,那么我们只要求出这个式子的最大值并判断这个值是否大于0。大于0说明\(Q\)不够大,小于0说明\(Q\)太大了,用二分法不断逼近答案直到达到合适的精度。

三、例题

以下例题不是十分困难,稍复杂的地方就是二分法check( )函数的写法。

例1:POJ2976 Dropping tests(原POJ2519)

题目疯狂明示让你用01分数规划。二分枚举成绩,求出每一项的值并排序(要求最大值),如果答案小于0那么更改左区间,反之更改右区间。
Code:

#include<bits/stdc++.h>
#define N 2000
using namespace std;
int n,k;
double a[N],b[N],f[N];
double check(double mid)
{
    memset(f,0,sizeof(f));
    for(int i=1;i<=n;i++)
        f[i]=a[i]-mid*b[i];//转化后的公式
    sort(f+1,f+n+1,greater<double>());
    double sum=0;
    for(int i=1;i<=n-k;i++)
        sum+=f[i];//求一下最大值
    return (sum>0)? 1:0;
}
int main()
{
    while(scanf("%d%d",&n,&k)&&n+k)
    {
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
        for(int i=1;i<=n;i++) scanf("%lf",&b[i]);
        double l=0,r=1e10;
        while(r-l>1e-8){
            double mid=(l+r)/2;
            if(check(mid)) l=mid;
            else r=mid;
        }
        cout<<fixed<<setprecision(0)<<l*100<<endl;
    }
    return 0;
}

例2:P1730 最小密度路径

对所有点对进行一次01分数规划,接下来跑最短路判断枚举的密度是否可行,最终求得最小密度路径。

扫描二维码关注公众号,回复: 7688888 查看本文章
#include<bits/stdc++.h>
#define N 10010
#define INF 0x3f3f3f3f
#define eps 1e-6
#define ll long long
using namespace std;
double ans[N][N],dist[N],maxn,cost[N];
int q,n,m,tot,vis[N];
int first[N],go[N],next[N];
inline void add_edge(int u,int v,double w){
    next[++tot]=first[u];
    first[u]=tot;
    go[tot]=v;
    cost[tot]=w;
}
inline int check(int s,int ed,double mid){
    queue<int> q;
    for(int i=1;i<=n;i++){
        dist[i]=INF;vis[i]=0;
    }
    q.push(s);vis[s]=1;dist[s]=0;
    while(!q.empty()){
        int u=q.front();
        q.pop();vis[u]=0;
        for(int e=first[u];e;e=next[e]){
            int v=go[e];double w=cost[e];
            if(dist[v]>dist[u]+w-mid){
                dist[v]=dist[u]+w-mid;
                if(!vis[v]){
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return (dist[ed]>0)? 1:0;//求的最小比值大于枚举值,更新l,否则更新r
}
inline void erfen(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(i==j) continue;
            check(i,j,0);
            if(dist[j]==INF){ans[i][j]=-1;continue;}
            long double l=0,r=maxn;
            while(r-l>eps){
                long double mid=(l+r)/(2.0);
                if(!check(i,j,mid)) r=mid;
                else l=mid;
            }
            ans[i][j]=l;
        }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,u,v,w;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,(double)w);
        maxn+=w;
    }
    erfen(); 
    scanf("%d",&q);
    for(int i=1,u,v;i<=q;i++){
        scanf("%d%d",&u,&v);
        ans[u][v]<0?printf("OMG!\n"):printf("%.3lf\n",ans[u][v]);
    }
    return 0;
}

例3:CF489E Hiking

基础题。
Code:

#include<bits/stdc++.h>
#define N 100010
using namespace std;
const double INF=1e15;
int n,last[N];
double len,pos[N],w[N],f[N];
vector<int> v;
double check(double mid)
{
    for(int i=1;i<=n;i++){
        f[i]=INF;
        for(int j=0;j<i;j++){
            if(f[i]>f[j]+sqrt(fabs(pos[i]-pos[j]-len))-mid*w[i]){
                f[i]=f[j]+sqrt(fabs(pos[i]-pos[j]-len))-mid*w[i];
                last[i]=j;
            }
        }
    }
    return (f[n]<=0)?1:0;
}
int main()
{
    scanf("%d%lf",&n,&len);
    for(int i=1;i<=n;i++)
        scanf("%lf%lf",&pos[i],&w[i]);
    double l=0,r=1e10;
    while(r-l>=1e-9){
        double mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid;
    }
    check(l);
    int now=n;
    while(now>0){
        v.push_back(now);
        now=last[now];
    }
    for(int i=v.size()-1;i>=0;i--)
        printf("%d ",v[i]);
    return 0;
}

例4:P2868 [USACO07DEC]观光奶牛Sightseeing Cows

此题同P1768 天路类似。
奶牛们最终要回到起点,我们同样枚举一个比值\(Q\),可以知道如果变形后不等式大于0,也就是说存在环,那么更新左区间端点\(l\)。判断一个环可以转换成负环处理,即用SPFA判负环。

#include<bits/stdc++.h> 
#define N 200010
using namespace std;
int first[N],next[N],go[N],cost[N],vis[N];
int m,n,tot;
double dist[N],len[N],f[N];
inline void add_edge(int u,int v,int w){
    next[++tot]=first[u];
    first[u]=tot;
    go[tot]=v;
    cost[tot]=w;
}
double SPFA(int u)//判负环
{
    vis[u]=1;
    for(int i=first[u];i;i=next[i])
    {
        int v=go[i];
        double w=len[i];
        if(dist[v]>dist[u]+w)
        {
            dist[v]=dist[u]+w;
            if(vis[v]||SPFA(v)){
                vis[v]=0;
                return 1;
            }
        }
    }
    vis[u]=0;
    return 0;
}
double check(double mid)
{
    for(int i=1;i<=tot;i++)
        len[i]=(double)cost[i]*mid-f[go[i]];
    for(int i=1;i<=n;i++){
        if(SPFA(i)) return 1;
    }
    return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lf",&f[i]);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,w);
    }
    double l=0,r=1e6;
    while(r-l>1e-6){
        double mid=(l+r)/2;
        if(check(mid))
            l=mid;
        else r=mid;
    }
    cout<<fixed<<setprecision(2)<<l<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cyanigence-oi/p/11766432.html