原题:P1570 KC 喝咖啡
题面:
P1570 KC 喝咖啡
题目描述
话说 KC 和 SH 在福州的时候常常跑去 85°C 喝咖啡或者其他的一些什么东西。
这天,KC 想要喝一杯咖啡,服务员告诉他,现在有 种调料,这杯咖啡只可以加入其中的 种(当然 KC 一定会加入 种,不会加入少于 种的调料),根据加入的调料不同,制成这杯咖啡要用的时间也不同,得到的咖啡的美味度也不同。
KC 在得知所有的 种调料后,作为曾经的化竞之神的他,马上就知道了所有调料消耗的时间 以及调料的美味度 。由于 KC 急着回去刷题,所以他想尽快喝到这杯咖啡,但他又想喝到美味的咖啡,所以他想出了一个办法,他要喝到 最大的咖啡,也就是单位时间的美味度最大的咖啡。
现在,KC 把调料信息告诉了 SH,要 SH 帮他算出喝到的咖啡的 ,但 SH 不想帮 KC 算这东西,于是 KC 就只能拜托你来算了。
注释: 表示求和,所以 表示美味度的总和除以消耗时间的总和。
输入格式
输入数据共三行。
第一行为一个整数 ,表示调料种数和能加入的调料数。
接下来两行,每行为 个数,第一行第 个整数表示调料 的美味度 ,第二行第 个整数表示调料 消耗的时间 。
输出格式
一个实数 ,表示 KC 喝的咖啡的最大 ,保留三位小数。
输入输出样例 #1
输入 #1
3 2
1 2 3
3 2 1
输出 #1
1.667
说明/提示
样例 1 解释:
KC 选 号和 号调料,。
可以验证不存在更优的选择。
数据范围:
对 的数据:。
对 的数据:。
对 的数据:。
对 的数据:。
数据保证答案不超过 。
一开始我是考虑贪心,直接设 ,然后按 排序取前 个,只拿了50pts,显然贪心策略是错误的。因为分数之和是非线性的,所以这种思路直接gg了。
那么应该怎么考虑呢?我们来分析一下所给的式子:
我们设对于某一种选择方式,有 ,变形一下,变成 ,可以发现,这似乎满足某种线性关系。
我们要求的是 的最大值,即令 最大,当 增大的时候, 的值减小,当 减小的时候, 的值增大。我们要使 最大,就要让选取的这 种调料中,每一种调料所对应的函数关系值 最大,所以假设我们已经确定了 ,此时将所有调料按 的值从大到小排序,选取前 种调料,记录对应的和 。若此时 ,则说明现在的 实在太大了,哪怕选取对应函数值最大的 种调料也不能满足条件;如果此时 ,则说明现在的 还有上升的空间,可以继续增加直到 为止。
那么显然,这个过程可以用二分来维护。
对于像这种所求的目标函数是一个 类型的问题,称作分数规划。而分数规划又分为线性和非线性两种,对于线性分数规划, 都是形如 的,此处 为行向量, 为列向量,可以运用线性规划求解。
以及非线性的,这种类型就需要运用Dinkelbach方法来逼近求解。实际上这道题运用的二分方法就是Dinkelbach方法的离散形式。
至于这道题虽然理论上来说是一个线性分数规划问题,但是如果直接用线性规划来求解过于麻烦 (我不会) ,所以我们也可以直接使用Dinkelbach方法即二分答案来逼近求解。
#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;
}
总体时间复杂度: ,实际上不需要那么高的精度,但是我比较谨慎