洛谷P1570 KC 喝咖啡(二分+分数规划)

25 阅读4分钟

原题:P1570 KC 喝咖啡

题面:

P1570 KC 喝咖啡

题目描述

话说 KC 和 SH 在福州的时候常常跑去 85°C 喝咖啡或者其他的一些什么东西。

这天,KC 想要喝一杯咖啡,服务员告诉他,现在有 nn 种调料,这杯咖啡只可以加入其中的 mm 种(当然 KC 一定会加入 mm 种,不会加入少于 mm 种的调料),根据加入的调料不同,制成这杯咖啡要用的时间也不同,得到的咖啡的美味度也不同。

KC 在得知所有的 nn 种调料后,作为曾经的化竞之神的他,马上就知道了所有调料消耗的时间 cic _ i 以及调料的美味度 viv _ i。由于 KC 急着回去刷题,所以他想尽快喝到这杯咖啡,但他又想喝到美味的咖啡,所以他想出了一个办法,他要喝到 vici\dfrac{\sum v _ i}{\sum c _ i} 最大的咖啡,也就是单位时间的美味度最大的咖啡。

现在,KC 把调料信息告诉了 SH,要 SH 帮他算出喝到的咖啡的 vici\dfrac{\sum v _ i}{\sum c _ i},但 SH 不想帮 KC 算这东西,于是 KC 就只能拜托你来算了。

注释:\sum 表示求和,所以 vici\dfrac{\sum v _ i}{\sum c _ i} 表示美味度的总和除以消耗时间的总和。

输入格式

输入数据共三行。

第一行为一个整数 n,mn, m,表示调料种数和能加入的调料数。

接下来两行,每行为 nn 个数,第一行第 ii 个整数表示调料 ii 的美味度 viv _ i,第二行第 ii 个整数表示调料 ii 消耗的时间 cic _ i

输出格式

一个实数 TT,表示 KC 喝的咖啡的最大 vici\dfrac{\sum v _ i}{\sum c _ i},保留三位小数。

输入输出样例 #1

输入 #1

3 2
1 2 3
3 2 1

输出 #1

1.667

说明/提示

样例 1 解释

KC 选 22 号和 33 号调料,vici=2+32+1=1.667\dfrac{\sum v _ i}{\sum c _ i} = \dfrac{2 + 3}{2 + 1} = 1.667

可以验证不存在更优的选择。

数据范围

20%20 \% 的数据:1n51 \leq n \leq 5

50%50 \% 的数据:1n101 \leq n \leq 10

80%80 \% 的数据:1n501 \leq n \leq 50

100%100 \% 的数据:1n200,1mn,1c[i],v[i]1×1041 \leq n \leq 200, 1 \leq m \leq n, 1 \leq c[i], v[i] \leq 1 \times 10 ^ 4

数据保证答案不超过 10001000

SolutionSolution

一开始我是考虑贪心,直接设 vali=vi/cival_i=v_i/c_i ,然后按 valival_i 排序取前 mm 个,只拿了50pts,显然贪心策略是错误的。因为分数之和是非线性的,所以这种思路直接gg了。

那么应该怎么考虑呢?我们来分析一下所给的式子: vici\dfrac{\sum v _ i}{\sum c _ i}

我们设对于某一种选择方式,有 vici=x\dfrac {\sum v_i}{\sum c_i}=x ,变形一下,变成 vixci=0\sum v_i-x\sum c_i=0 ,可以发现,这似乎满足某种线性关系。

我们要求的是 vici=x\dfrac {\sum v_i}{\sum c_i}=x 的最大值,即令 xx 最大,当 xx 增大的时候, vixci\sum v_i-x\sum c_i 的值减小,当 xx 减小的时候, vixci\sum v_i-x\sum c_i 的值增大。我们要使 xx 最大,就要让选取的这 mm 种调料中,每一种调料所对应的函数关系值 vixciv_i-xc_i 最大,所以假设我们已经确定了 xx ,此时将所有调料按 vixciv_i-xc_i 的值从大到小排序,选取前 mm 种调料,记录对应的和 sumsum 。若此时 sum<0sum<0 ,则说明现在的 xx 实在太大了,哪怕选取对应函数值最大的 mm 种调料也不能满足条件;如果此时 sum>0sum>0 ,则说明现在的 xx 还有上升的空间,可以继续增加直到 vixci=0\sum v_i-x\sum c_i=0 为止。

那么显然,这个过程可以用二分来维护。

对于像这种所求的目标函数是一个 f(x)g(x)\dfrac {f(x)}{g(x)} 类型的问题,称作分数规划。而分数规划又分为线性和非线性两种,对于线性分数规划,f(x),g(x)f(x),g(x) 都是形如 aTx+ba^Tx+b 的,此处 aTa^T 为行向量,xx 为列向量,可以运用线性规划求解。

以及非线性的,这种类型就需要运用Dinkelbach方法来逼近求解。实际上这道题运用的二分方法就是Dinkelbach方法的离散形式。

至于这道题虽然理论上来说是一个线性分数规划问题,但是如果直接用线性规划来求解过于麻烦 (我不会) ,所以我们也可以直接使用Dinkelbach方法即二分答案来逼近求解。

CodingCoding

#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;

#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";

int n, m;
const int maxn = 210;
const double eps = 1e-6;
double v[maxn], c[maxn];

bool check(double x)
{
    vector<double> diff;
    for (int i = 1; i <= n; i++)
        diff.push_back(v[i] - x * c[i]);

    sort(diff.begin(), diff.end(), [](double a, double b)
         { return a > b; });

    double sum = 0;
    for (int i = 0; i < m; i++)
        sum += diff[i];

    return sum >= 0;
}

double binary(double left, double right)
{
    double mid, ans;

    while (right - left >= eps)
    {
        mid = ((left + right) / 2);

        if (check(mid))
        {
            ans = mid;
            left = mid + eps;
        }
        else
            right = mid - eps;
    }

    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> v[i];
    for (int i = 1; i <= n; i++)
        cin >> c[i];

    cout << fixed << setprecision(3) << binary(0, 1e4);

    return 0;
}

总体时间复杂度:O(nlogn×log(range))O(nlogn \times log(range)) ,实际上不需要那么高的精度,但是我比较谨慎