二分答案求最大单位价值

该博客介绍了一道关于海盗分宝的数学问题,通过二分答案的方法寻找最大化价值与重量比的解决方案。博主详细解析了如何构建检查函数,并提供了AC代码实现,最后邀请读者对文章内容进行评价。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

海盗头子普朗克在珍宝岛上找到一批宝物,宝物有 nnn 件,第 iii 件宝物的价值为 cic_ici,重量为 wiw_iwi。普朗克想取其中的 kkk 件宝物,使得这些宝物的价值之和除以重量之和(实数除法)达到最大。请帮他求出这个最大值。

输入格式

第一行两个正整数 n,kn, kn,k
接下来 nnn 行,每行两个正整数 ci,wic_i,w_ici,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,ci,wi≤1051 \leq n, c_i, w_i \leq 10^51n,ci,wi105,保证答案不超过 10910^9109

题目解答

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

  • 假设我们当前二分枚举到的值为 ans\text{ans}ans,选的 kkk 个物品价值之和为 ∑ci\sum c_ici,重量之和为 ∑wi\sum w_iwi,那么求如下式子的真假即可:
    ∑ci∑wi≥ans \dfrac{\sum c_i}{\sum w_i} \geq \text{ans} wicians
  • 不妨对它进行变换:
    ∑ci∑wi≥ans=∑ci≥ans×∑wi=∑ci−∑(ans×wi)≥0=∑(ci−ans×wi)≥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
  • 只要我们每次将 ci−ans×wic_i - \text{ans} \times w_icians×wi 的值降序排序,取前 kkk 个元素,看它们的和是不是大于 000 即可,时间复杂度 O(nlog⁡nlog⁡(∑wi))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、付费专栏及课程。

余额充值