0/1分数规划

@Pelom  October 11, 2021

0/1分数规划

一、分数规划

给出若干二元组 $(v_i,c_i)$ ,要求从中选出一些,使得 $\frac{\sum v_i}{\sum c_i}$ 最大(最小)。

二、解法

先考虑最大,最小同理。

1、二分法

假设问题的最优解为 $ans$ ,则有
$$\frac{\sum v_i}{\sum c_i} \le ans$$
移项
$$\sum v_i \le ans \cdot \sum c_i$$
$$\sum v_i-ans \cdot c_i \le 0$$
如此可以将整体的贡献转化为单个物品的贡献 $v_i-ans \cdot c_i$
关于计算 $ans$ ,我们可以考虑二分,然后判断可行性。

2、Dinkelbach算法

在二分的时候,我们判断可行性时仅用到 $F(mid)$ 的正负性,然后使 $l$ 右移或 $r$ 左移,而对其本身并没有利用到
因此,在二分时,若 $F(mid)>0$ 则将 $l$ 移动到 $F(mid)$ 对应直线的横截距上
$Dinkelbach$ 算法 实质上是一种迭代算法,基于这样的思想:不去二分答案,而是先随便给定一个答案,然后根据更优的解不断移动答案,逼近最优解。理论上它比二分快些
但由于 $Dinkelbach$ 算法 需要保存解,且二分的函数不一定平滑,实际速度不一定比二分快

三、应用

1.分数规划+背包=最优比率背包

洛谷P4377 Talent Show

应题目条件,在dp时把所有总质量大于等于W的更新全算在f[W]上即可

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=250+1;
const int M=1000+1;
const double eps=1e-6;
int n,m;
int w[N],t[N];
double f[M];
template <typename T>
inline T Min(T a,T b){
    return a<b?a:b;
}
template <typename T>
inline T Max(T a,T b){
    return a>b?a:b;
}
inline bool check(double x){
    fill(f+1,f+m+1,-1e6);
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--){
            int k=Min(m,j+w[i]);
            f[k]=Max(f[k],f[j]+t[i]-w[i]*x);
        }
    return f[m]>=0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&t[i]);
    double l=0,r=1e3,mid;
    for(;r-l>eps;){
        mid=(l+r)/2;
        if(check(mid))
            l=mid;
        else r=mid;
    }
    printf("%d",int(l*1000));
    return 0;
}

2.分数规划+最小生成树=最优比率生成树

洛谷P4951 地震

$$ans \le \frac{F-\sum c_i}{\sum t_i}$$
$$\sum ans \cdot t_i+c_i \le F$$
二分时给边上新附一个权值 $mid \cdot c_i+t_i$ ,按该权值求最小生成树再将结果与 $F$ 比较判断可行性

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=400+1;
const int M=10000+1;
const double eps=1e-6;
int n,m,f,u,v,c,t;
struct edge{
    int u,v,c,t;
    double w;
    inline bool operator < (const edge& x) const{
        return w<x.w;
    }
} e[M];
int fa[N];
inline int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
inline bool check(double x){
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
        e[i].w=e[i].c+x*e[i].t;
    sort(e+1,e+m+1);
    int cnt=0;
    double sum=0;
    for(int i=1;i<=m;i++){
        if(cnt==n-1)
            break;
        int fu=find(e[i].u),fv=find(e[i].v);
        if(fu==fv)
            continue;
        fa[fv]=fu;
        cnt++;
        sum+=e[i].w;
    }
    return sum<=f;
}
int main(){
    scanf("%d%d%d",&n,&m,&f);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].c,&e[i].t);
    double l=0,r=1e9,mid;
    for(;r-l>eps;){
        mid=(l+r)/2;
        if(check(mid))
            l=mid;
        else r=mid;
    }
    printf("%.4lf",l);
    return 0;
}

3.分数规划+负环=最优比率环

洛谷P2868 观光奶牛

二分时判定图上是否有一个环 $R$ 满足
$$mid \le \frac{\sum_i^{i \in V} F_i}{\sum_j^{j \in E} T_j}$$
其中 $V$ 为 $R$ 的点集,$E$ 为 $R$ 的边集
考虑将出点的点权减到边权上
$$\sum mid*T_i-F_i \le 0$$
此时可直接在图上判负环

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1000+1;
const int M=5000+1;
const double eps=1e-6;
int n,m,u,v,t;
int f[N];
int cnt;
int head[N];
double dis[N];
bool vis[N];
struct edge{
    int nxt,to,t;
    double w;
} e[M];
inline void add(int u,int v,int t){
    e[++cnt]=(edge){head[u],v,t};
    head[u]=cnt;
}
bool spfa(int u){
    vis[u]=1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(dis[v]>dis[u]+e[i].w){
            dis[v]=dis[u]+e[i].w;
            if(vis[v] || spfa(v)){
                vis[v]=0;
                return 1;
            }
        }
    }
    vis[u]=0;
    return 0;
}
inline bool check(double x){
    for(int i=1;i<=m;i++)
        e[i].w=x*e[i].t-f[e[i].to];
    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("%d",&f[i]);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&t);
        add(u,v,t);
    }
    double l=0,r=1e6,mid;
    for(;r-l>eps;){
        mid=(l+r)/2;
        if(check(mid))
            l=mid;
        else r=mid;
    }
    printf("%.2lf",l);
    return 0;
}

添加新评论