【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\),那么有:
变形再移项可得:\(\displaystyle \sum^{n}_{i=1}\lambda_ia_i-mid\times\sum^{n}_{i=1}\lambda_ib_i\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;
}

浙公网安备 33010602011771号