[ABC294F] Sugar Water 2题解

泥土笨笨

2023-03-20 14:45:08

Solution

# 题意简述 高桥君有 $n$ 瓶糖水,编号为 $1$ 到 $n$,其中第 $i$ 瓶糖水中含有 $a_i$ 克糖和 $b_i$ 克水。青木君有 $m$ 瓶糖水,编号为 $1$ 到 $m$,其中第 $i$ 瓶糖水中含有 $c_i$ 克糖和 $d_i$ 克水。 现在高桥君随意选出一瓶水,青木君也随意选出一瓶水,混合在一起,这样总共有 $n \times m$ 种不同的混合方式。所有混合方式中,按照浓度从高到低排序,问排第 $k$ 的混合方式的浓度是多少。浓度计算方式为,如果一瓶水中有 $x$ 克糖,有 $y$ 克水,浓度为 $\frac{100 \times x}{x+y}$。 # 解题思路 本题数据范围 $n,m \le 5 \times 10^4$,总的方案太多了,如果全枚举出来,再排序一遍,复杂度是 $n \times m \log(n \times m) $,这样肯定要超时。 题目中问我们浓度第 $k$ 大的方案的浓度,不妨设为 $cc$,那么我们可以二分这个浓度 $cc$,然后计算所有混合液当中,浓度比 $cc$ 大的方案数 $cnt$。如果发现 $cnt \lt k$,说明我们选择的 $cc$ 是可行的,继续去小的那边二分。否则说明我们选择的 $cc$ 太小了,它肯定不是目标浓度,就去大的那边二分。 下面问题就是,当给定浓度的时候,如果求出浓度比它大的方案数呢? 首先对于青木君的 $m$ 瓶糖水,假设每瓶水中糖有 $x$ 克,水有 $y$ 克。我们假设要把它调整为浓度为 $cc$ 的,则在水不变的情况下,糖需要 $cc \times y / (1 - cc)$ 克,而现有的糖为 $x$ 克,那么这瓶水当中多余的糖为 $x - cc \times y / (1 - cc)$ 克。我们可以算出每一瓶里面多余的糖,然后按照这个数字对青木君的 $m$ 瓶糖水排序。请注意,这里多余的糖可能为负数,表示的是缺少多少糖。负数也没关系的,正常放到数组里排序。 接下来枚举高桥君的 $n$ 瓶糖水,对于当前这一瓶,还是用一样的方式,我们可以算出这一瓶糖水中,多余的糖是多少,然后取个负,变成缺多少糖,记为 $less$。则我们可以到刚才排好序的数组里面二分查找,找到有多少个数字是大于 $less$ 的,那么这些都可以和当前这瓶组合出浓度超过 $cc$ 的混合液。 这样排序复杂度 $O(m\log(m))$,枚举和二分查找的复杂度 $O(n\log(m))$,外面再套 $100$ 层二分浓度 $cc$ 就行了。题目中要求精度是 $10^{-9}$,我们二分 $100$ 次,精度是 $2^{-100}$,肯定够了。这样总的复杂度是 $O(100 \times ((m+n)\log(m)))$,可以通过本题。 # 代码示例 ```cpp #include <iostream> #include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const ll MAXN = 1e5 + 5; const ll MOD = 1e9 + 7; ll n, m, k; double a[MAXN], b[MAXN], c[MAXN], d[MAXN], more[MAXN]; //计算浓度大于mid的方案数 ll check(double cc) { //浓度是cc,对于m个瓶子,水是y,则糖需要cc * y / (1 - cc) //多余的糖是x - cc * y / (1 - cc),把每个瓶子中多余的糖放到more数组里 for (int i = 0; i < m; ++i) { more[i] = c[i] - cc * d[i] / (1 - cc); } //按照多余的糖排序 sort(more, more + m); ll cnt = 0; //遍历n个瓶子,对于每个瓶子,计算它缺少的糖 for (int i = 0; i < n; ++i) { double less = -(a[i] - cc * b[i] / (1 - cc)); //二分查找,找到所有多余的糖比less多的个数 ll t = more + m - upper_bound(more, more + m, less); cnt += t; } return cnt; } int main() { cin >> n >> m >> k; for (int i = 0; i < n; ++i) { cin >> a[i] >> b[i]; } for (int i = 0; i < m; ++i) { cin >> c[i] >> d[i]; } //二分排名第k的浓度 double low = 0, high = 1, mid; for (int i = 0; i < 100; ++i) { mid = (low + high) / 2; if (check(mid) < k) { high = mid; } else { low = mid; } } printf("%.15lf\n", high*100); return 0; } ```