【0/1 分数规划】学习笔记

给定 \(n\) 个物品,每个物品有两个属性 \(a,b\),我们需要从里面选一些出来,求 \(\dfrac{\displaystyle\sum a_i}{\displaystyle\sum b_i}\) 的最小/大值。

更数学一点表述,我们可以将这个问题抽象为这样一个模型:已知数列 \(\{a_n\}\) 和数列 \(\{b_n\}\),我们要求一组解 \(\{\lambda_n\}(\lambda_i\in\{0,1\})\),使得\(\dfrac{\displaystyle\sum^{n}_{i=1}\lambda_ia_i}{\displaystyle\sum^{n}_{i=1}\lambda_ib_i}\) 最小/大。

由于每个物品在决策中可以选或不选,类似 \(0/1\) 背包,因此这类问题被称为 \(0/1\) 分数规划模型。

\(0/1\) 分数规划模型的一般求解方法是二分。以求解最大值为例,我们现在要求 \(\dfrac{\displaystyle\sum^{n}_{i=1}\lambda_ia_i}{\displaystyle\sum^{n}_{i=1}\lambda_ib_i}\) 的最大值。对此我们在 \(\mathbf{R}\) 上二分一个值 \(mid\),那么有:

\[\dfrac{\displaystyle\sum^{n}_{i=1}\lambda_ia_i}{\displaystyle\sum^{n}_{i=1}\lambda_ib_i}\ge mid \]

变形再移项可得:\(\displaystyle \sum^{n}_{i=1}\lambda_ia_i-mid\times\sum^{n}_{i=1}\lambda_ib_i\ge 0\),那么有:

\[\boxed{\sum^{n}_{i=1}\lambda_i\left(a_i-mid\times b_i\right)\ge 0} \]

故我们只需要求出 \(\displaystyle \sum^{n}_{i=1}\lambda_i\left(a_i-mid\times b_i\right)\) 的最大值即可,如果最大值非负,则往右子区间二分,否则往左子区间二分。求最小值与求最大值类似。

例题:P1570 KC 喝咖啡:在模型的基础上限制选且仅能选 \(m\) 个物品。

贪心地考虑,要求的是 \(\displaystyle \sum^{n}_{i=1}\lambda_i\left(v_i-mid\times c_i\right)\) 的最大值,那么我们在 check 时按照 \(v_i-mid\times c_i\) 值的大小排序,并取前 \(m\) 大的即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 210;
const double eps = 1e-5;
int n, m;
double l, r;
struct Coffee
{
	int v, c;
	double val;
}a[N];
inline bool cmp(Coffee A, Coffee B)
{
	return A.val > B.val;
}
bool check(double x)
{
	double sum = 0.0;
	for(int i = 1; i <= n; i ++) a[i].val = a[i].v - x * a[i].c;
	sort(a + 1, a + 1 + n, cmp);
	for(int i = 1; i <= m; i ++) sum += a[i].val;
	return sum >= 0;
}
int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i].v);
	for(int i = 1; i <= n; i ++)
	{
		scanf("%d", &a[i].c);
		r = max(r, a[i].v * 1.0 / a[i].c);
	}
	while(r - l > eps)
	{
		double mid = (l + r) / 2.0;
		if(check(mid)) l = mid;
		else r = mid;
	}
	printf("%.3lf", l);
	return 0;
}
posted @ 2025-07-22 17:11  cold_jelly  阅读(14)  评论(0)    收藏  举报