题解:洛谷 AT_abc426_e Closest Moment
【题目来源】
洛谷:AT_abc426_e [ABC426E] Closest Moment - 洛谷
【题目描述】
Takahashi 和 Aoki 在一个二维平面上行走。
Takahashi 的起点为 \((TS_X, TS_Y)\),终点为 \((TG_X, TG_Y)\)。Aoki 的起点为 \((AS_X, AS_Y)\),终点为 \((AG_X, AG_Y)\)。
他们同时从各自的起点出发,以速度 \(1\) 沿直线朝各自的终点移动,并在到达终点时停下(注意,他们同时出发,但不一定同时到达终点停下)。
请你求出二者在他们之间的距离最短的时刻(包括刚出发时和到达终点后)时的距离。
此处距离为欧几里得距离。即,两点 \((x_1,y_1),(x_2,y_2)\) 之间的距离定义为 \(\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\)。
你需要解答 \(T\) 组测试用例。
【输入】
输入从标准输入给出,格式如下:
\(T\) \(\text{case}_1\) \(\text{case}_2\) \(\vdots\) \(\text{case}_T\)
其中 \(\text{case}_i\) 表示第 \(i\) 组测试用例。每组测试用例如下:
\(TS_X\) \(TS_Y\) \(TG_X\) \(TG_Y\) \(AS_X\) \(AS_Y\) \(AG_X\) \(AG_Y\)
【输出】
输出 \(T\) 行,第 \(i\) 行(\(1\leq i\leq T\))为第 \(i\) 组测试用例的答案。
你的答案若与真值的绝对误差或相对误差不超过 \(10^{-6}\),则被视为正确。
【输入样例】
4
0 0 -2 2
-1 -1 4 4
4 0 2 0
6 0 8 0
1 0 1 1
-1 0 1 1
-8 9 2 6
-10 -10 17 20
【输出样例】
1.000000000000000
2.000000000000000
0.000000000000000
1.783905950993199
【核心思想】
-
问题分析:给定两人在二维平面上的起点和终点,均以速度 \(1\) 沿直线匀速运动,到达终点后停下。求两人之间欧几里得距离的最小值。这是一个三分搜索/凸函数优化问题,关键在于将两人的位置表示为时间的线性函数,距离平方是关于时间的二次函数(凸函数),可用三分搜索求最小值;同时需处理两人到达终点时间不同导致的分段情况。
-
算法选择:
- 参数化位置:设 Takahashi 运动时间为 \(t_T\),Aoki 为 \(t_A\),两人在时间 \(t\) 时的位置为起点 + 速度向量 \(\times \frac{t}{\text{总时间}}\)
- 三分搜索:距离平方是关于 \(t\) 的凸函数,在区间 \([0, \min(t_T, t_A)]\) 上用三分搜索逼近最小值
- 分段处理:先到达终点的人会停下(速度变为 \(0\)),后到达的人继续运动。需分两段处理:第一段两人都在运动,第二段仅一人运动
-
关键步骤:
- 读取输入:\(T\) 组测试数据,每组 \(8\) 个坐标值
- 计算速度向量:\(TV = (TG_X - TS_X,\ TG_Y - TS_Y)\),\(AV = (AG_X - AS_X,\ AG_Y - AS_Y)\)
- 计算运动时间:\(t_T = |TV|\),\(t_A = |AV|\)(速度为 \(1\),时间 = 距离)
- 统一命名:确保 \(t_T \le t_A\)(Takahashi 先到达),若否则交换两人信息
- 第一段求解(\(t \in [0, t_T]\),两人均在运动):
- 调用
solve(TS, TV, AS, (t_T/t_A) \cdot AV, t_T),Aoki 的速度按比例缩放以匹配时间参数化 - 三分搜索 \(60\) 次逼近最小距离
- 调用
- 第二段求解(\(t \in [t_T, t_A]\),Takahashi 已停下,Aoki 继续运动):
- Takahashi 位置固定为终点 \(TG\),Aoki 从 \(AS + (t_T/t_A) \cdot AV\) 以剩余速度 \((1 - t_T/t_A) \cdot AV\) 运动
- 调用
solve(TG, \{0,0\}, AS + (t_T/t_A) \cdot AV, (1 - t_T/t_A) \cdot AV, t_A - t_T)
- 输出:\(ans = \min(ans_1, ans_2)\),保留 \(15\) 位小数
-
时间/空间复杂度:
- 时间复杂度:\(O(T \cdot 60)\),每组数据进行两次三分搜索,每次 \(60\) 次迭代
- 空间复杂度:\(O(1)\),仅使用常数个向量变量
-
三分搜索的核心思想:
- 凸函数性质:距离平方 \(d(t)^2 = ((TS_X + TV_X \cdot t) - (AS_X + AV_X \cdot t))^2 + (\cdots)^2\) 是关于 \(t\) 的二次函数,图像为抛物线,具有单峰性(凸性),适合三分搜索
- 参数化统一:将不同运动时间的两人统一到相同时间尺度,通过速度向量缩放实现
- 分段最小值:两人运动时间不同时,最小值可能出现在"都在运动段"或"一人静止段",需分别求解后取最小
- 适用于"匀速运动物体间最小距离 + 凸函数优化"类问题,核心是建立位置关于时间的函数并识别其凸性
【算法标签】
普及+ #三分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int T; // 测试用例数量
double TSX, TSY, TGX, TGY; // 目标物体的起始和终点坐标
double ASX, ASY, AGX, AGY; // 攻击物体的起始和终点坐标
// 二维向量结构体
struct Vec
{
double x, y;
};
// 向量加法运算符重载
Vec operator+(Vec a, Vec b)
{
return (Vec){a.x + b.x, a.y + b.y};
}
// 向量减法运算符重载
Vec operator-(Vec a, Vec b)
{
return (Vec){a.x - b.x, a.y - b.y};
}
// 向量数乘运算符重载(向量在左)
Vec operator*(Vec a, double b)
{
return (Vec){a.x * b, a.y * b};
}
// 向量数乘运算符重载(向量在右)
Vec operator*(double a, Vec b)
{
return (Vec){a * b.x, a * b.y};
}
// 向量数除运算符重载
Vec operator/(Vec a, double b)
{
return (Vec){a.x / b, a.y / b};
}
// 计算向量长度
double len(Vec a)
{
return sqrt(a.x * a.x + a.y * a.y);
}
// 计算两点间距离
double dis(Vec a, Vec b)
{
return len(a - b);
}
// 计算二维坐标长度
double len(double x, double y)
{
return sqrt(x * x + y * y);
}
/**
* 求解两物体在时间t内的最小距离
* @param TS 目标物体起始位置
* @param TV 目标物体速度向量
* @param AS 攻击物体起始位置
* @param AV 攻击物体速度向量
* @param t 时间参数
* @return 最小距离
*/
double solve(Vec TS, Vec TV, Vec AS, Vec AV, double t)
{
double l = 0, r = t; // 初始化三分查找区间
// 进行60次三分查找以逼近最小值
for (int i = 1; i <= 60; i++)
{
double ml = (l + l + r) / 3; // 左三分点
double mr = (l + r + r) / 3; // 右三分点
// 计算两个时间点的位置
Vec TSL = TS + (ml / t) * TV;
Vec TSR = TS + (mr / t) * TV;
Vec ASL = AS + (ml / t) * AV;
Vec ASR = AS + (mr / t) * AV;
// 比较距离并缩小查找区间
if (dis(TSL, ASL) < dis(TSR, ASR))
{
r = mr;
}
else
{
l = ml;
}
}
// 返回最小距离
return dis(TS + (l / t) * TV, AS + (l / t) * AV);
}
int main()
{
cin >> T; // 输入测试用例数量
while (T--)
{
// 输入目标物体和攻击物体的坐标
cin >> TSX >> TSY >> TGX >> TGY >> ASX >> ASY >> AGX >> AGY;
// 计算速度向量
double TVX = TGX - TSX, TVY = TGY - TSY;
double AVX = AGX - ASX, AVY = AGY - ASY;
// 计算运动时间
double tt = len(TVX, TVY), ta = len(AVX, AVY);
// 确保目标物体运动时间较短
if (tt > ta)
{
swap(tt, ta);
swap(TSX, ASX); swap(TSY, ASY);
swap(TGX, AGX); swap(TGY, AGY);
swap(TVX, AVX); swap(TVY, AVY);
}
// 转换为向量表示
Vec TS = {TSX, TSY}, TG = {TGX, TGY}, TV = {TVX, TVY};
Vec AS = {ASX, ASY}, AG = {AGX, AGY}, AV = {AVX, AVY};
// 计算两个阶段的最小距离
double ans1 = solve(TS, TV, AS, (tt / ta) * AV, tt);
double ans2 = solve(TG, (Vec){0, 0}, AS + ((tt / ta) * AV), (1 - tt / ta) * AV, ta - tt);
// 取最小值作为最终结果
double ans = min(ans1, ans2);
// 输出结果(保留15位小数)
printf("%.15lf\n", ans);
}
return 0;
}
【运行结果】
4
0 0 -2 2
-1 -1 4 4
1.000000000000000
4 0 2 0
6 0 8 0
2.000000000000000
1 0 1 1
-1 0 1 1
0.000000000033617
-8 9 2 6
-10 -10 17 20
1.783905950993200
浙公网安备 33010602011771号