1 条题解

  • 0
    @ 2025-5-11 21:09:19

    问题分析

    题目要求在正方形 [0,1]×[0,1][0, 1] \times [0, 1] 内给定 NN 个点,通过连接这些点和正方形的四个角,找到一种连接方式使得图的总长度最短。同时,需要调整这些点的位置,使得与其他点的相对位置关系保持不变的情况下,移动的总距离最小。最终输出这个最小的移动距离总和。

    核心思路

    1. 预定义的关键点和线段
      题目中预定义了两个关键区域,每个区域有 5 条线段和两个特殊点:

      • 区域 1:点 onetwo,线段 Line1, Line2, Line3, Line4, Line5
      • 区域 2:点 threefour,线段 Line6, Line7, Line8, Line9, Line10

      这些线段和点的位置是根据几何特性设计的,目的是模拟一种“最优连接方式”,让每个点尽量沿最近的线段连接到正方形的顶点上。

    2. 计算点到线段的最小距离
      对于每个点,计算它到某个区域所有线段的最短距离。如果点投影到线段上,就用投影点计算距离;如果投影点超出了线段范围,则取到线段起点或终点的距离。

    3. 寻找最优连接点对
      遍历所有点对,计算每个点对分别连接到两个关键点的总长度(考虑点到关键点的距离和垂直距离)。选择一组点对,使得这些距离的差值最小。

    4. 计算总移动距离
      对于每个区域,选择最优的点对后,计算所有点的总移动距离,即每个点到最近线段的距离之和。

    5. 输出结果
      比较两个区域的最小移动距离,输出较小值。

    代码细节解析

    #include<iostream>
    #include<cstring>
    #include<string>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #define esp 1e-6  // 定义一个小的误差范围,用于比较浮点数
    using namespace std;
    
    int n;  // 点的数量
    struct Point {
        double x, y;  // 点的坐标
        Point() {}  // 默认构造函数
        Point(double _x, double _y) { x = _x; y = _y; }  // 带参数的构造函数
        // 重载运算符 '-',用于计算两个点的向量差
        Point operator - (const Point& b) const {
            return Point(x - b.x, y - b.y);
        }
        // 重载运算符 '*',用于计算两个向量的点积
        double operator * (const Point& b) const {
            return x * b.x + y * b.y;
        }
    } p[110];  // 存储所有点的数组
    
    struct Line {
        Point s, e;  // 线段的起点和终点
        Line(Point p1, Point p2) {  // 构造函数
            s = p1; e = p2;
        }
    };
    
    double best = 1e9;  // 用于存储全局最小值,初始化为一个很大的数
    
    // 预定义的关键点和线段
    const Point only(0.5, 0.5);  // 正方形的中心点
    const Point one(sqrt(3.0) / 6.0, 0.5);  // 区域1的关键点1
    const Point two(1.0 - sqrt(3.0) / 6.0, 0.5);  // 区域1的关键点2
    const Point three(0.5, sqrt(3.0) / 6.0);  // 区域2的关键点1
    const Point four(0.5, 1.0 - sqrt(3.0) / 6.0);  // 区域2的关键点2
    const Point lu(0, 1), ld(0, 0), ru(1, 1), rd(1, 0);  // 正方形的四个顶点
    
    // 预定义的线段
    const Line Line1(one, two);
    const Line Line2(lu, one);
    const Line Line3(ru, two);
    const Line Line4(ld, one);
    const Line Line5(rd, two);
    const Line Line6(three, four);
    const Line Line7(rd, three);
    const Line Line8(lu, four);
    const Line Line9(ld, three);
    const Line Line10(ru, four);
    
    // 将线段分组,分别对应两个区域
    Line line1[5] = {Line1, Line2, Line3, Line4, Line5};
    Line line2[5] = {Line6, Line7, Line8, Line9, Line10};
    
    // 计算两点之间的欧几里得距离
    double dist(Point n1, Point n2) {
        return sqrt((n1.x - n2.x) * (n1.x - n2.x) + (n1.y - n2.y) * (n1.y - n2.y));
    }
    
    // 计算点 P 到线段 L 的最小距离
    double Get_nearest_point(Point P, Line L) {
        Point result;  // 用于存储最近点
        // 计算点 P 在线段 L 上的投影位置
        double t = ((P - L.s) * (L.e - L.s)) / ((L.e - L.s) * (L.e - L.s));
        if (t >= 0.0 && t <= 1.0) {
            // 如果投影点在线段范围内,计算投影点的坐标
            result.x = L.s.x + (L.e.x - L.s.x) * t;
            result.y = L.s.y + (L.e.y - L.s.y) * t;
        } else {
            // 如果投影点超出线段范围,选择离点 P 更近的端点
            if (dist(P, L.s) - dist(P, L.e) < esp) result = L.s;
            else result = L.e;
        }
        return dist(result, P);  // 返回点 P 到最近点的距离
    }
    
    // 计算点 p[x] 到一组线段 l 的最小距离
    double Get_dist_to_line(int x, Line l[5]) {
        double _Min = 1e9;  // 初始化最小距离为一个很大的数
        for (int i = 0; i < 5; i++) {
            _Min = min(_Min, Get_nearest_point(p[x], l[i]));  // 遍历所有线段,找到最小距离
        }
        return _Min;
    }
    
    // 解决函数,计算在给定线段和关键点的情况下,所有点的最小移动距离
    double solve(Line l[5], Point p1, Point p2) {
        int pos1, pos2;  // 用于存储最优的点对
        double ans = 0, _Min = 1e9;  // 初始化最小值和最终答案
        // 遍历所有点对,寻找最优连接点对
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i == j) continue;  // 跳过相同的点
                double d1 = dist(p[i], p1), d2 = dist(p[j], p2);  // 计算点到关键点的距离
                double d3 = Get_dist_to_line(i, l), d4 = Get_dist_to_line(j, l);  // 计算点到线段的最小距离
                // 更新最小值和最优点对
                if (_Min > d1 - d3 + d2 - d4) {
                    _Min = d1 - d3 + d2 - d4;
                    pos1 = i; pos2 = j;
                }
            }
        }
        // 计算最优点对的总距离
        ans = dist(p[pos1], p1) + dist(p[pos2], p2);
        // 计算其他点的移动距离
        for (int i = 0; i < n; i++) {
            if (i == pos1 || i == pos2) continue;  // 跳过最优点对
            ans += Get_dist_to_line(i, l);  // 累加其他点到线段的最小距离
        }
        return ans;  // 返回总移动距离
    }
    
    int main() {
        while (~scanf("%d", &n) && n) {  // 读取点的数量,如果为0则结束
            best = 1e9;  // 初始化全局最小值
            for (int i = 0; i < n; i++) {
                scanf("%lf%lf", &p[i].x, &p[i].y);  // 读取每个点的坐标
            }
            if (n == 1) {
                // 如果只有一个点,直接计算该点到中心点的距离
                printf("%.3lf\n", dist(p[0], only));
                continue;
            }
            // 分别计算两个区域的最小移动距离,并输出较小值
            printf("%.3lf\n", min(solve(line1, one, two), solve(line2, three, four)) + esp);
        }
        return 0;
    }
    
    1. PointLine 结构体

      • Point 结构体用于表示点,重载了运算符 -*,分别用于计算向量减法和点积。
      • Line 结构体用于表示线段,由起点 s 和终点 e 组成。
    2. 关键点和线段的定义

      • 定义了两个区域的关键点和线段,这些点和线段是根据几何特性预设的,用于计算点到线段的最小距离。
    3. dist 函数
      用于计算两点之间的欧几里得距离。

    4. Get_nearest_point 函数
      用于计算点到线段的最小距离。根据向量投影计算点在直线上的投影位置,如果投影位置在线段范围内,则计算投影点到点的距离;否则,计算点到线段起点或终点的距离。

    5. Get_dist_to_line 函数
      遍历某个区域的所有线段,找到点到这些线段的最小距离。

    6. solve 函数

      • 对于每个区域,找到一组最优的点对,使得连接到关键点的总长度最小。
      • 遍历所有点对,计算每个点对到关键点的距离,减去到最近线段的距离,选择最小值。
      • 计算所有点的总移动距离,包括最优点对到关键点的距离和其他点到最近线段的距离。
    7. 主函数逻辑

      • 读取输入,处理每个测试用例。
      • 如果只有一个点,直接输出该点到中心点 only 的距离。
      • 对于多个点,分别计算两个区域的最小移动距离,输出较小值。

    总结

    本题通过预设的关键点和线段,模拟了一种最优连接方式。通过计算点到线段的最小距离,选择最优的点对,最终实现最小化移动距离的目标。代码整体逻辑清晰,难点在于理解几何关系和线段的预设设计。

    • 1

    信息

    ID
    1054
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    7
    已通过
    1
    上传者