题解:洛谷 P1570 KC 喝咖啡

【题目来源】

洛谷:P1570 KC 喝咖啡 - 洛谷

【题目描述】

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

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

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

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

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

【输入】

输入数据共三行。

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

接下来两行,每行为 \(n\) 个数,第一行第 \(i\) 个整数表示调料 \(i\) 的美味度 \(v _ i\),第二行第 \(i\) 个整数表示调料 \(i\) 消耗的时间 \(c _ i\)

【输出】

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

【输入样例】

3 2
1 2 3
3 2 1

【输出样例】

1.667

【解题思路】

image

【算法标签】

《洛谷 P1570 KC喝咖啡》 #二分# #分数规划# #洛谷原创#

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 205;
int n, m, v[N], c[N];  // v: 价值数组, c: 代价数组
double a[N], vis[N];   // a: 单位价值(v[i]/c[i]), vis: 标记数组

// 计算在给定阈值mid下,选择m个物品的最大单位价值
double calc(double mid)
{
    memset(vis, 0, sizeof(vis));  // 初始化标记数组
    double x = 0, y = 0;  // x: 总价值, y: 总代价
    int num = m;  // 还需要选择的物品数量
    
    // 优先选择单位价值 >= mid 的物品
    for (int i = 1; i <= n; i++)
        if (a[i] >= mid)  // 单位价值大于等于阈值
        {
            x += v[i];
            y += c[i];
            num--;  // 已选数量减1
            vis[i] = 1;  // 标记已选
            if (num == 0)  // 已选满m个
                break;
        }
    
    if (num == 0)  // 如果已经选满m个
        return mid + 1;  // 返回大于mid的值,表示可以达到这个阈值
    
    // 如果还没选满,从剩下的物品中选择
    while (num--)  // 还需要选num个
    {
        int pos = 0;  // 记录选择的物品索引
        double maxx = 0;  // 最大单位价值
        
        // 从剩余物品中选择使总单位价值最大的物品
        for (int i = 1; i <= n; i++)
        {
            if (vis[i]) continue;  // 跳过已选物品
            double tmp = 1.0 * (x + v[i]) / (y + c[i]);  // 选择后的单位价值
            if (tmp > maxx)
            {
                maxx = tmp;
                pos = i;
            }
        }
        x += v[pos], y += c[pos];  // 更新总价值和总代价
        vis[pos] = 1;  // 标记已选
    }
    
    double res = 1.0 * x / y;  // 最终的单位价值
    return res;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> v[i];
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i];
        a[i] = 1.0 * v[i] / c[i];  // 计算单位价值
    }
    
    // 二分搜索阈值
    double l = 0, r = 1000, eps = 1e-5;  // 阈值范围[0, 1000]
    while (r - l > eps)  // 精度控制
    {
        double mid = (l + r) / 2;  // 中间值
        double k = calc(mid);  // 计算在阈值mid下的最大单位价值
        
        if (k >= mid)  // 如果可以达到mid
            l = mid;   // 尝试更大的阈值
        else 
            r = mid;   // 阈值太大,减小
    }
    printf("%.3lf", l);  // 输出最终阈值
    return 0;
}

【运行结果】

3 2
1 2 3
3 2 1
1.667
posted @ 2026-03-23 10:18  团爸讲算法  阅读(2)  评论(0)    收藏  举报