01分数规划小记

update:

  • 2024.7.10 补两道 分数规划 + 网络流的好题

01 分数规划

求形如 i=1nai×cii=1nbi×ci,ci0,1 式子的最大值或最小值。

一般方法有两个:二分法与迭代法。这里不再赘述。
01分数规划时常与图论结合。

例题:

I P1570 KC 喝咖啡

λ=i=1naii=1nbi,则 i=1naiλ×i=1nbi=0,再设 G(λ)=i=1naiλ×i=1nbi,则 G(λ) 就是一个斜率为负的一次函数,我们拆开得 G(λ)=i=1n(aiλ×bi),贪心选择 ci=aiλbi 即可。

二分法:

点击查看代码
#include <bits/stdc++.h> 
using namespace std;
const int N = 210;
const double eps = 1e-5;
//01分数规划 
int n,m;
double a[N],b[N],c[N];
int main(){
    scanf("%d%d",&n,&m);
    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 = 1010;
    while(l + eps < r){
    	double mid = (l + r) / 2.0;
    	for(int i = 1;i <= n;i++)c[i] = a[i] - mid * b[i];
    	sort(c+1,c+1+n);//排序找最大值
    	double s = 0;
    	for(int i = n;i > n-m;i--)s += c[i];
    	if(s >= 0)l = mid;
    	else r = mid;
	}
	printf("%.3lf\n",r);
    
    return 0;
    
}

II P4377 [USACO18OPEN] Talent Show G

首先要求 i=1ntii=1nwi 最大值,但是有一些限制,只有选出来的 wi 之和大于 W 才可以。
我们把每个牛当做 质量为 wi,价值为 tiλ×wi,那么我们就可以想到背包,设 fi 为重量之和为 i 的物品最大价值,判断 f[W] 是否大于 0,直接转移即可。

点击查看代码
#include <bits/stdc++.h> 
using namespace std;
//01分数规划 + 背包 
#define ll long long
const int N = 300,M = 1010;
const double eps = 1e-5,inf = 1e8;
int n,W;
double w[N],t[N],f[M];
bool check(double mid){
	for(int i = 1;i <= 1000;i++)f[i] = -inf;
	for(int i = 1;i <= n;i++){
		for(int j = W;j >= 0;j--){
			int k = min(W,j+(int)w[i]);//超过的直接放到f[W]上 
			f[k] = max(f[k],f[j] + t[i] - mid * w[i]);
		} 
	}
	return f[W] >= 0;
}
int main(){
    scanf("%d%d",&n,&W);
    for(int i = 1;i <= n;i++)scanf("%lf%lf",&w[i],&t[i]);
    double l = 0,r = 1e6;
    while(l + eps < r){
    	double mid = (l + r) / 2;
    	if(check(mid))l = mid;
    	else r = mid;
	}
	printf("%d\n",int(r*1000));
    
    return 0;
    
}


III P2868 [USACO07DEC] Sightseeing Cows G

一个图,有点权有边权,设一个环内点集为 S,边集为 E求最大值 uSAueETe,很显然的01分数规划模型,只是要求选出来的点组成一个环即可,二分后,我们要找是否存在一个环使 G(mid) (别忘了 ci=aiλ×bi)大于 0,这是什么?
最长路判正环!!(不会的去学-_-)(本题 spfa 判正环即可)
然后正常二分即可。

点击查看代码
#include <bits/stdc++.h> 
using namespace std;
const int N = 2e3+10,M = 1e4+10;
const double eps = 1e-4,inf = 1e8;
//01分数规划 + 判负环 
int n,m;
struct made{
    int nx,ver;
    double ed;
}e[M<<1];
int hd[N],tot;
double a[N];
void add(int x,int y,double z){
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,hd[x] = tot;
}
double d[N];
int in[N];bool v[N];
bool spfa(double mid){
    memset(v,0,sizeof v);
    memset(in,0,sizeof in);
    for(int i = 0;i <= n;i++)d[i] = -inf;
    queue<int>q;q.push(0),v[0] = 1,d[0] = 0;
    while(!q.empty()){
        int x = q.front();q.pop();
        v[x] = 0;
        for(int i = hd[x];i;i = e[i].nx){
            int y = e[i].ver;double z = e[i].ed;
            z = a[x] - mid * z;
            if(d[y] < d[x] + z){
                d[y] = d[x] + z;
                in[y]++;
                if(in[y] > n)return 1;//判正环
                if(!v[y])v[y] = 1,q.push(y);
            }
        }
    }
    return 0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++)scanf("%lf",&a[i]);
    for(int i = 1;i <= n;i++)add(0,i,0);
    for(int i = 1;i <= m;i++){
        int x,y;double z;
        scanf("%d%d%lf",&x,&y,&z);
        add(x,y,z);
    }
    double l = 0,r = 1e5;
    while(l + eps < r){
        double mid = (l + r) / 2;
        if(spfa(mid))l = mid;
        else r = mid;
    }
    printf("%.2lf\n",r);
    
    return 0;
    
}

IV P3199 [HNOI2009] 最小圈

我们要找图中一个环使得环上权值的平均值最小,即一个环的边集 E,使 eETibi 最小,这里的 bi 都等于 1,那么使 ci=Tiλ,二分,判断 G(mid) 最小值是否小于 0 即可,即判断图中是否有负环
做完了?没有,如果你直接写 spfa 判负环就会得到 50 分,因为 spfa 复杂度是 O(km),会爆,所以我们就要开摆原神启动
有一个 dfs 实现 spfa 判负环的科技,可以快速判负环,但是会算不了最短路,所以慎用
时间复杂度不会,反正就是很快 (:з」∠),咕咕咕。

点击查看代码
#include <bits/stdc++.h> 
using namespace std;
const int N = 3e3+10,M = 2e4+10;
const double eps = 1e-10,inf = 1e8;
//01分数规划 + 判负环 
int n,m;
struct made{
    int nx,ver;
    double ed;
}e[M<<1];
int hd[N],tot;
void add(int x,int y,double z){
    tot++;
    e[tot].nx = hd[x],e[tot].ver = y,e[tot].ed = z,hd[x] = tot;
}
double d[N];
bool v[N];
bool spfa(int x,double mid){
	v[x] = 1;
    for(int i = hd[x];i;i = e[i].nx){
    	int y = e[i].ver;double z = e[i].ed;
    	z -= mid;
    	if(d[y] > d[x] + z){
    		d[y] = d[x] + z;
    		if(v[y] || spfa(y,mid))return 1;//被遍历过了即有负环
		}
	}
	v[x] = 0;
	return 0;
}//dfs-spfa判负环 
bool check(double mid){
    memset(v,0,sizeof v);
    for(int i = 1;i <= n;i++)d[i] = 0;//初值为0
	for(int i = 1;i <= n;i++)
	    if(spfa(i,mid))return 1;
	return 0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i++)add(0,i,0);
    for(int i = 1;i <= m;i++){
        int x,y;double z;
        scanf("%d%d%lf",&x,&y,&z);
        add(x,y,z);
    }
    double l = -1e7,r = 1e7;
    while(l + eps < r){
        double mid = (l + r) / 2;
        if(check(mid))r = mid;
        else l = mid;
    }
    printf("%.8lf\n",r);
    
    return 0;
    
}

V 3999. 制作人偶

首先二分 mid,转化为求 viwi=λ 最大值,我们设 S=vi,则转化为求 Sviwi=λ 的最小值,及判断是否存在 vimid×wi0

我们考虑建立最小割模型,则我们设源点 s不选该点,汇点为选了该点

则对于每个点 i,我们建 (i,t,mid×wi)
对于每条边 (x,y,z),我们建 (s,x,z2),(s,y,z2),(x,y,z2),(y,x,z2) 这些边,然后跑网络流,判断是否有 Smaxflow0 即可。

注意double类型网络流中需注意精度问题

代码

VI 2047. [ZOJ 2676]网络战争

首先题目条件是简单的,选择一些边,需要保证 1 节点到 n 节点中的任意路线都经过至少一个边,即找到以 1 为源点,n 为汇点的图的割边

我们要求 ck=λ最小值,我们二分 mid,则我们只判断是否满足 ck×mid<0,且满足割边的条件,即求以 cmid 为边权的最小割(若 cmid0,我们可以贪心直接累加上答案,不用建边)。

输出的是满足答案最小的方案数,即求最小割的方案数,我们只需在图上 dfs 出以源点的集合,把源点与汇点的集合交汇的边存起来即可。

代码

posted @   oXUo  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
网站统计
点击右上角即可分享
微信分享提示