Function
f(i,j) 是 y_i、y_j 的加权平均,取值有界可二分;f(i,j)≥v 变形为两个序列的比较,排序后双指针 O(n) 计数求第 k 大。
OJ: roj
题目 ID: 19999
难度:普及+/提高-
标签:二分答案双指针排序
日期: 2026-08-28 19:55
形式化题目
给定
共有
思路
一句话本质:
**问题?**直接枚举所有
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-28 18:45
* update_at: 2026-08-28 18:45
*/
// brute.cpp:小数据暴力解,直接双重循环枚举所有 f(i,j) 排序取第 k 大。
// 这是本题最直观的朴素做法,复杂度 O(n^2 log n),只适合 n 很小的数据,
// 用于帮助理解题意,并与 main.cpp 对拍验证。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n;
long long k; // 第 k 大,k 最大 n(n-1)/2,用 long long
long long x[MAXN], y[MAXN]; // 每个点的 x_i, y_i
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
// 枚举所有无序对 (i,j),计算 f(i,j) 并收集起来
vector<double> all;
all.reserve((long long)n * (n - 1) / 2);
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
double f = (double)(x[i] * y[i] + x[j] * y[j]) / (x[i] + x[j]);
all.push_back(f);
}
}
// 从高到低排序,取第 k 个(下标 k-1)
sort(all.begin(), all.end(), greater<double>());
printf("%.4f\n", all[k - 1]);
return 0;
}这个暴力双重循环枚举每一对数对
问题?"第
要么显式求出前
问题?
**问题?**怎么快速统计
两边乘正数
令
**问题?**统计
把
实现细节:check 里的浮点比较用 q[j] - p[i] < 1e-3 视为
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-28 18:45
* update_at: 2026-08-28 18:45
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n;
long long k; // k 最大 n(n-1)/2 ≈ 5e9,必须用 long long
long long x[MAXN], y[MAXN]; // 每个点的 x_i, y_i
double p[MAXN], q[MAXN]; // p_i = x_i*(y_i-v),q_i = x_i*(v-y_i) = -p_i
// 统计满足 f(i,j) >= v 的数对个数
long long check(double v) {
long long tot = 0;
for (int i = 1; i <= n; i++) {
p[i] = x[i] * (y[i] - v);
q[i] = x[i] * (v - y[i]);
// 有向对 (i,i) 被计入当且仅当 p_i >= q_i(即 p_i >= 0),先减掉
// 注意必须在排序前用原始下标判断
if (q[i] - p[i] < 1e-3) tot--;
}
// 两个数组都升序排序,便于双指针统计 p_i >= q_j 的有向对数
sort(p + 1, p + n + 1);
sort(q + 1, q + n + 1);
// 双指针:对每个 p[i],统计满足 q[j] <= p[i] 的 j 的个数
// p 升序时 p[i] 单调不减,j 也单调不减,整体 O(n)
int j = 0;
for (int i = 1; i <= n; i++) {
while (j < n && q[j + 1] - p[i] < 1e-3) j++;
tot += j;
}
// 每个合法无序对在有向计数中恰好贡献 2 次,折半即为答案
return tot / 2;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
// f(i,j) 是 y_i 与 y_j 以 x_i, x_j 为权的加权平均,取值恒在 [1, 1e9]
double lo = 1, hi = 1e9;
while (hi - lo > 1e-3) {
double mid = (lo + hi) / 2;
if (check(mid) >= k) lo = mid;
else hi = mid;
}
printf("%.4f\n", lo);
return 0;
}复杂度
二分约 40 次(区间 check 排序
总结
- 核心观察一:
是加权平均,天然有界,二分区间 成立; - 核心观察二:"第
大"用"二分答案 + 计数 的对数"解决, 大到无法求前 大; - 核心观察三:不等式分离变量,
,两序列排序后双指针 计数; - 实现易错点:
用 long long;自我配对修正必须在排序前按原始下标做;双指针先判下标再访问。