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

[TOC]



##一、总述
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}a_p\times x_p}{\sum_{p=1}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}a_p\times x_p}{\sum_{p=1}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分数规划,接下来跑最短路判断枚举的密度是否可行,最终求得最小密度路径。

#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;
}

原文地址:https://www.cnblogs.com/cyanigence-oi/p/11766432.html

时间: 2024-09-29 03:14:28

[例题/总结]0/1分数规划的相关文章

bzoj 3232 圈地游戏——0/1分数规划(或网络流)

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3232 当然是0/1分数规划.但加的东西和减的东西不在一起,怎么办? 考虑把它们合在一起.因为边围成的形状像一个环,所以把格子的贡献也放到边上,然后正常判环. 放到边上的方法就是:比如竖着的边,可以在每一行上维护该行格子值前缀和,然后指定那个围成的形状是,比如,逆时针的,那么向上的边就加上到它为止的前缀值,向下的边就减去到它为止的前缀值,然后就能判环了! 这样一定只有一个环.但多个环答案不会

0/1分数规划

学习了lyd书上的0/1分数规划,发现这类题目都有一个特点,就是求$\frac{\sum_{a_{i}*x_{i}}}{\sum_{b_{i}*x_{i}}}$的最大或者最小,再加一些限制取不取的条件. POJ2976 二分答案+sort取前(n-k+1)个. #include <iostream> #include <cstdio> #include <algorithm> #include <cmath> using namespace std; con

POJ - 2976 Dropping tests &amp;&amp; 0/1 分数规划

POJ - 2976 Dropping tests 你有 \(n\) 次考试成绩, 定义考试平均成绩为 \[\frac{\sum_{i = 1}^{n} a_{i}}{\sum_{i = 1}^{n} b_{i}}\] 你可以考虑放弃 \(K\) 次成绩, 求最大平均成绩 * 100 小插曲: 被精度卡成喜羊羊 0/1分数规划\(from\)人生导师 Solution 01分数规划(不是很)裸题, 在每次 \(check\) 时, 选取较大的 \(num - K + 1\) 次即可 Code #

2019.4.9 一题——概率期望+0/1分数规划+最大权闭合子图

没注意 “第 x 条边和第 y 条边的起点是相同的” 的限制.没想出来. 有这个限制,可以考虑每个点分别计算.令 \( f[i] \) 表示从 i 出发的最大边数期望,那么先把拓扑序在自己之后的点的 \( f[ ] \) 算出来,然后考虑自己这个点的出边怎么做能使自己的 \( f[ ] \) 最大. \( f[i]=\frac{ \sum f[j]+1 }{ d } \) ,其中 d 是保留下来的边数, j 是保留边指向的点. 如果把 \( f[ ]+1 \) 看做收益, 1 看做代价,那么这个

『0/1分数规划 二分法』

0/1分数规划 模型 0/1分数规划指的是这样一个问题模型: 给定整数\(a_1,a_2,...,a_n\)和\(b_1,b_2,...,b_n\),求一组解\(x_1,x_2,...,x_n(\forall\ i\in[1,n],x_i=1,0)\),使得下式最大化:\[\frac{\sum_{i=1}^na_i*x_i}{\sum_{i=1}^nb_i*x_i}\] 简单地说,就是给定\(n\)对整数\(a_i,b_i\),从中选取若干对,使得选出的\(a\)之和与\(b\)之和的比值最大.

Yougth的最大化(好题,二分查找 0 1分数规划)

Yougth的最大化 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 Yougth现在有n个物品的重量和价值分别是Wi和Vi,你能帮他从中选出k个物品使得单位重量的价值最大吗? 输入 有多组测试数据每组测试数据第一行有两个数n和k,接下来一行有n个数Wi和Vi.(1<=k=n<=10000) (1<=Wi,Vi<=1000000) 输出 输出使得单位价值的最大值.(保留两位小数) 样例输入 3 2 2 2 5 3 2 1 样例输出 0.75 1 #in

POJ 3621(0/1分数规划,二分) Sightseeing Cows

题意 给一个n个点m条边的图,每一个点和每一条边都有权值.现在要找一个环的点权和/边权和最大,求这个最大值. 思路 SPFA+二分 题目的关系式:点权和/边权和 <= ans 转换一下就变成 ans*边权和 - 点权和 >= 0; 二分答案,然后用SPFA去check是否存在一个负权回路. 参考code: /* #pragma warning (disable: 4786) #pragma comment (linker, "/STACK:0x800000") */ #in

0/1 分数规划

模型: 给定整数 \(v_i, c_i\),规定 \(x_i=0\) 或 \(1\),存在一组解 \(\{x_i\}\),使得 \(\displaystyle \frac{\sum_{i=1}^{n} v_ix_i}{\sum_{i=1}^{n} c_ix_i}\) 最大. 解法: 最大化 \(\displaystyle \frac{v_i}{c_i}\)(即性价比)的贪心方法不可行. \(\displaystyle \frac{\sum_{i=1}^{n} v_ix_i}{\sum_{i=1}

cogs 2652. 秘术「天文密葬法」(0/1分数规划 长链剖分 二分答案 dp

http://cogs.pro:8080/cogs/problem/problem.php?pid=vSXNiVegV 题意:给个树,第i个点有两个权值ai和bi,现在求一条长度为m的路径,使得Σai/Σbi最小. 思路:二分答案得p,把每个点权值变成ai-p*bi,看是否存在长为一条长为m的路使总和<=0. tag数组表示从当前位置沿最长链走到底的值,dp数组初值表示从当前位置的重儿子走到底的值(加负号),用tag[...]+dp[..]维护从当前节点往下走若干步得到的最小值(只更新dp数组