二分答案:时间 T 可行等价于每个人的可达区间有公共交点,判定只需比较区间左端点最大值与右端点最小值。
OJ: roj
题目 ID: 20027
难度:普及
标签:二分答案数学
日期: 2026-09-06 15:54
形式化题目
数轴上有
思路
一句话本质:“最小化最晚到达时间"反过来变成"判定给定时间
先看一个可以直接验证想法的小数据精确解:
/**
* 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-09-06 15:54
* update_at: 2026-09-06 16:00
*/
// brute.cpp:小数据暴力解,枚举全部候选聚会点,精确求出 f(p) 的最小值。
// f(p) = max_i |x_i - p| / v_i 是凸函数,最小值出现在两个 V 形的交点、
// 区间端点或某个 x_i 处,因此枚举这些候选点即可得到精确最小值。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 15;
int n;
double x[MAXN], v[MAXN];
// 计算在点 p 聚会时,最晚到达的时间。
double f(double p) {
double t = 0;
for (int i = 1; i <= n; i++) {
t = max(t, fabs(x[i] - p) / v[i]);
}
return t;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) cin >> x[i];
for (int i = 1; i <= n; i++) cin >> v[i];
double minx = x[1], maxx = x[1];
for (int i = 2; i <= n; i++) {
minx = min(minx, x[i]);
maxx = max(maxx, x[i]);
}
vector<double> cand; // 候选聚会点
cand.push_back(minx);
cand.push_back(maxx);
for (int i = 1; i <= n; i++) cand.push_back(x[i]);
// 枚举每对 V 形函数的交点,三种位置关系各解一次方程。
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
// 两人同侧(p 在两者同一边):p = (v_j x_i - v_i x_j) / (v_j - v_i)
if (fabs(v[j] - v[i]) > 1e-12) {
cand.push_back((v[j] * x[i] - v[i] * x[j]) / (v[j] - v[i]));
}
// 两人异侧(p 在两者之间):p = (v_j x_i + v_i x_j) / (v_i + v_j)
cand.push_back((v[j] * x[i] + v[i] * x[j]) / (v[i] + v[j]));
}
}
double ans = 1e18;
for (double p : cand) {
if (p < minx || p > maxx) continue; // 最优聚点一定在坐标范围内
ans = min(ans, f(p));
}
printf("%.5f\n", ans);
return 0;
}这个暴力直接枚举候选聚会点:
问题? 直接求最优位置难在哪?
问题? 换个角度,怎么判定"给定时间
第
问题? 区间交非空怎么快速判断?
问题? 判定和答案有什么关系,为什么能二分?
“
下面这张表用样例 #1(
| 人 | 初始位置 | 速度 | 可达区间 |
|---|---|---|---|
| 1 | 7 | 1 | |
| 2 | 1 | 2 | |
| 3 | 3 | 1 |
看三个区间的交:左端点最大是
问题? 二分结束时输出什么?
二分把可行区间压到 lo 即最小可行时间的近似值,误差 printf("%.5f") 四舍五入保留 5 位小数即可。
代码
/**
* 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-09-06 15:54
* update_at: 2026-09-06 16:00
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const double EPS = 1e-9;
int n;
double x[MAXN], v[MAXN];
// 判断时间 T 内所有人能否赶到同一个点:
// 第 i 个人的可达区间是 [x_i - v_i*T, x_i + v_i*T],
// 所有人能同时到达当且仅当这些区间的交非空。
bool check(double T) {
double l = -1e18, r = 1e18;
for (int i = 1; i <= n; i++) {
l = max(l, x[i] - v[i] * T); // 所有区间左端点的最大值
r = min(r, x[i] + v[i] * T); // 所有区间右端点的最小值
}
return l <= r + EPS;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
double minx = 1e18, maxx = -1e18;
for (int i = 1; i <= n; i++) {
cin >> x[i];
minx = min(minx, x[i]);
maxx = max(maxx, x[i]);
}
for (int i = 1; i <= n; i++) cin >> v[i];
// 二分答案:时间 T 可行具有单调性。
double lo = 0, hi = maxx - minx; // 速度至少为 1,时间上界取坐标跨度
for (int step = 0; step < 100; step++) {
double mid = (lo + hi) / 2;
if (check(mid)) hi = mid;
else lo = mid;
}
printf("%.5f\n", lo);
return 0;
}复杂度
- 时间:每次判定
,二分 100 次,总 。 - 空间:
。
总结
- 核心转化(正难则反):求最优位置困难,改为"给定时间
判定可行性",把优化问题变成判定问题。 - 核心判定:可达区间
的交非空 ⟺ 左端点最大值 ≤ 右端点最小值,一次扫描 。 - 可迁移思想:"最小化最大值"且判定具有单调性的题,直接二分答案;实数二分固定迭代次数(如 100 次)控制精度,比比较
hi-lo更省心。