题解:洛谷 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. 问题分析:给定两人在二维平面上的起点和终点,均以速度 \(1\) 沿直线匀速运动,到达终点后停下。求两人之间欧几里得距离的最小值。这是一个三分搜索/凸函数优化问题,关键在于将两人的位置表示为时间的线性函数,距离平方是关于时间的二次函数(凸函数),可用三分搜索求最小值;同时需处理两人到达终点时间不同导致的分段情况。

  2. 算法选择

    • 参数化位置:设 Takahashi 运动时间为 \(t_T\),Aoki 为 \(t_A\),两人在时间 \(t\) 时的位置为起点 + 速度向量 \(\times \frac{t}{\text{总时间}}\)
    • 三分搜索:距离平方是关于 \(t\) 的凸函数,在区间 \([0, \min(t_T, t_A)]\) 上用三分搜索逼近最小值
    • 分段处理:先到达终点的人会停下(速度变为 \(0\)),后到达的人继续运动。需分两段处理:第一段两人都在运动,第二段仅一人运动
  3. 关键步骤

    • 读取输入\(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\) 位小数
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \cdot 60)\),每组数据进行两次三分搜索,每次 \(60\) 次迭代
    • 空间复杂度:\(O(1)\),仅使用常数个向量变量
  5. 三分搜索的核心思想

    • 凸函数性质:距离平方 \(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
posted @ 2026-08-27 11:03  团爸讲算法  阅读(9)  评论(0)    收藏  举报