题解:洛谷 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
【解题思路】

【算法标签】
《洛谷 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
浙公网安备 33010602011771号