二分答案求最大单位价值

1 篇文章 0 订阅
1 篇文章 0 订阅

题目描述

海盗头子普朗克在珍宝岛上找到一批宝物,宝物有 n n n 件,第 i i i 件宝物的价值为 c i c_i ci,重量为 w i w_i wi。普朗克想取其中的 k k k 件宝物,使得这些宝物的价值之和除以重量之和(实数除法)达到最大。请帮他求出这个最大值。

输入格式

第一行两个正整数 n , k n, k n,k
接下来 n n n 行,每行两个正整数 c i , w i c_i,w_i ci,wi

输出格式

一个数,即你所求出的最大值,保留四位小数。

输入样例

3 2
7 2
11 6
5 3

输出样例

2.4000

样例解释

选择第一个和第三个宝物,价值之和除以重量之和 ( 7 + 5 ) ÷ ( 2 + 3 ) = 2.4 (7+5) \div (2+3)=2.4 (7+5)÷(2+3)=2.4,达到最大。

输入样例2

6 3
18 96
22 41
34 14
7 87
27 47
79 12

输出样例2

2.0149

数据范围

对于 100 % 100\% 100% 的数据, 1 ≤ n , c i , w i ≤ 1 0 5 1 \leq n, c_i, w_i \leq 10^5 1n,ci,wi105,保证答案不超过 1 0 9 10^9 109

题目解答

这竟然是一道二分答案题,作为蒟蒻的我第一次做时竟然没有看出来。首先我们应该想如何写每次二分时的 check 函数:

  • 假设我们当前二分枚举到的值为 ans \text{ans} ans,选的 k k k 个物品价值之和为 ∑ c i \sum c_i ci,重量之和为 ∑ w i \sum w_i wi,那么求如下式子的真假即可:
    ∑ c i ∑ w i ≥ ans \dfrac{\sum c_i}{\sum w_i} \geq \text{ans} wicians
  • 不妨对它进行变换:
    ∑ c i ∑ w i ≥ ans = ∑ c i ≥ ans × ∑ w i = ∑ c i − ∑ ( ans × w i ) ≥ 0 = ∑ ( c i − ans × w i ) ≥ 0 \begin{aligned} & \dfrac{\sum c_i}{\sum w_i} \geq \text{ans} \\ = & \sum c_i \geq \text{ans} \times \sum w_i \\ = & \sum c_i - \sum (\text{ans} \times w_i) \geq 0 \\ = & \sum (c_i - \text{ans} \times w_i) \geq 0 \end{aligned} ===wicianscians×wici(ans×wi)0(cians×wi)0
  • 只要我们每次将 c i − ans × w i c_i - \text{ans} \times w_i cians×wi 的值降序排序,取前 k k k 个元素,看它们的和是不是大于 0 0 0 即可,时间复杂度 O ( n log ⁡ n log ⁡ ( ∑ w i ) ) O(n \log n \log ( \sum w_i) ) O(nlognlog(wi))。代码如下:

AC代码

#include <bits/stdc++.h>
using namespace std;
int n, k, c[100005], w[100005];
double l, r, arr[100005];

bool check(double x)
{
    double res = 0;
    for (int i = 1; i <= n; i++)
        arr[i] = c[i] - w[i] * x; // 计算ci - wi * x
    sort(arr + 1, arr + n + 1, greater<double>()); // 降序排序
    for (int i = 1; i <= k; i++) // 累加前k个数
        res += arr[i];
    return res >= 0;
}

int main()
{
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d %d", c + i, w + i);
        r += c[i];
    }
    while (r - l > 1e-6) // 二分答案
    {
        double mid = (l + r) / 2.0;
        if (check(mid))
            l = mid;
        else
            r = mid;
    }
    printf("%.4lf", l);
    return 0;
}

好了,本期博客就到这里,若注解有误,还请各位大佬多多指教。
另外,觉得写得好的话,还可以点赞+收藏哦 ^ ⌣ ^ \hat{} \smile \hat{} ^^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值