题解:洛谷 P1027 Car的旅行路线

【题目来源】

洛谷:P1027 [NOIP 2001 提高组] Car 的旅行路线 - 洛谷

【题目描述】

又到暑假了,住在城市 A 的 Car 想和朋友一起去城市旅游。
她知道每个城市都有 \(4\) 个飞机场,分别位于一个矩形的 \(4\) 个顶点上,同一个城市中两个机场之间有一条笔直的高速铁路,第 \(i\) 个城市中高速铁路的单位里程价格为 \(T_i\),任意两个不同城市的机场之间均有航线,所有航线单位里程的价格均为 \(t\)

img/Pasted image 20260516235339.png

注意:图中并没有标出所有的铁路与航线。

那么 Car 应如何安排到城市 B 的路线才能尽可能的节省花费呢?她发现这并不是一个简单的问题,于是她来向你请教。

找出一条从城市 A 到 B 的旅游路线,出发和到达城市中的机场可以任意选取,要求总的花费最少。

【输入】

第一行为一个正整数 \(n\),表示有 \(n\) 组测试数据。

每组的第一行有 \(4\) 个正整数 \(S,t,A,B\)

\(S\) 表示城市的个数,\(t\) 表示飞机单位里程的价格,\(A\)\(B\) 分别为城市A,B 的序号。

接下来有 \(S\) 行,其中第 \(i\) 行均有 \(7\) 个正整数 \(x_{i1},y_{i1},x_{i2},y_{i2},x_{i3},y_{i3},T_i\),这当中的 \((x_{i1},y_{i1}),(x_{i2},y_{i2}),(x_{i3},y_{i3})\),分别是第 \(i\) 个城市中任意 \(3\) 个机场的坐标,\(T_i\) 为第 \(i\) 个城市高速铁路单位里程的价格。

【输出】

共有 \(n\) 行,每行 \(1\) 个数据对应测试数据。
保留一位小数。

【输入样例】

1
3 10 1 3
1 1 1 3 3 1 30
2 5 7 4 5 2 1
8 6 8 8 11 6 3

【输出样例】

47.5

【核心思想】

  1. 问题分析:有 \(S\) 个城市,每个城市有 4 个机场(位于矩形的四个顶点)。同一城市内机场之间通过高速铁路连接,费用为 \(T_i \times\) 距离;不同城市之间通过航线连接,费用为 \(t \times\) 距离。需要从城市 A 到城市 B,选择任意机场作为起点和终点,求最小总费用。本质是一个多源最短路问题,将每个机场视为图中的节点,共 \(4S\) 个节点,用 Floyd 算法或 Dijkstra 求最短路。

  2. 算法选择

    • Floyd 算法:由于总节点数最多 \(4 \times 100 = 400\)(本题 S 未明确但通常不大),Floyd 的 \(O((4S)^3)\) 完全可行,且实现简单。
    • 建图:每个城市给 4 个机场编号。已知 3 个顶点,利用矩形性质(对角线互相平分且相等)求第 4 个顶点。同一城市内 4 个机场两两连边,权值 = 距离 \(\times T_i\);不同城市之间所有机场两两连边,权值 = 距离 \(\times t\)
    • 答案:城市 A 的 4 个机场到城市 B 的 4 个机场的最短距离的最小值。
  3. 关键步骤

    • 读入数据:对于每组数据,读入 \(S, t, A, B\),以及每个城市的 3 个顶点坐标和铁路单价 \(T_i\)
    • 计算第 4 个顶点:利用矩形的对角线互相平分且相等:已知三个顶点,判断哪个是直角顶点(通过距离平方和勾股定理),然后计算第四个顶点坐标。
    • 建图:初始化距离矩阵 f[i][j],同城市内按铁路单价,不同城市按飞机单价,自环为 0。
    • Floyd:计算所有点对之间的最短路。
    • 求答案:在 A 城市的 4 个机场和 B 城市的 4 个机场之间取最小距离。
  4. 时间/空间复杂度

    • 时间复杂度:\(O((4S)^3)\)\(S\) 通常不超过 100,可接受。
    • 空间复杂度:\(O((4S)^2)\)
  5. 几何 + 最短路

    • 矩形顶点补全:已知矩形三个顶点,利用向量关系或勾股定理求第四个顶点。
    • 图论建模:将机场作为节点,费用作为边权,问题转化为标准的最短路问题。
    • 适用场景:这类“多节点、多模式交通”问题,通常建模为图论最短路。

【算法标签】

普及+ #Floyd

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int n, s, t, a, b, x, y, p[100];
double f[100][100], ans;

// 节点结构体
struct Node
{
    int x, y;    // 坐标
    int idx;      // 所属区域编号
} sn[125];

// 计算两点间距离的平方(用于判断直角)
int dist_2(int x1, int y1, int x2, int y2)
{
    return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
}

// 计算两点间实际距离
double dist_1(int x1, int y1, int x2, int y2)
{
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

// 初始化距离矩阵
void getF()
{
    for (int i = 1; i <= 4 * s; i++)
    {
        for (int j = 1; j <= 4 * s; j++)
        {
            if (i == j)
                f[i][j] = 0;  // 同一点距离为0
            else
            {
                // 同区域按区域速度计算,不同区域按t计算
                if (sn[i].idx == sn[j].idx)
                    f[i][j] = dist_1(sn[i].x, sn[i].y, sn[j].x, sn[j].y) * p[sn[i].idx];
                else
                    f[i][j] = dist_1(sn[i].x, sn[i].y, sn[j].x, sn[j].y) * t;
            }
        }
    }
}

// 计算矩形的第四个顶点
void getNode(int x)
{
    int ab = dist_2(sn[4 * x + 1].x, sn[4 * x + 1].y, sn[4 * x + 2].x, sn[4 * x + 2].y);
    int bc = dist_2(sn[4 * x + 2].x, sn[4 * x + 2].y, sn[4 * x + 3].x, sn[4 * x + 3].y);
    int ac = dist_2(sn[4 * x + 1].x, sn[4 * x + 1].y, sn[4 * x + 3].x, sn[4 * x + 3].y);

    int dx = 0, dy = 0;
    // 根据勾股定理确定直角点,计算第四个顶点
    if (ab == bc + ac)
    {
        dx = sn[4 * x + 1].x + sn[4 * x + 2].x - sn[4 * x + 3].x;
        dy = sn[4 * x + 1].y + sn[4 * x + 2].y - sn[4 * x + 3].y;
    }
    else if (bc == ab + ac)
    {
        dx = sn[4 * x + 2].x + sn[4 * x + 3].x - sn[4 * x + 1].x;
        dy = sn[4 * x + 2].y + sn[4 * x + 3].y - sn[4 * x + 1].y;
    }
    else if (ac == ab + bc)
    {
        dx = sn[4 * x + 1].x + sn[4 * x + 3].x - sn[4 * x + 2].x;
        dy = sn[4 * x + 1].y + sn[4 * x + 3].y - sn[4 * x + 2].y;
    }

    sn[4 * x + 4].x = dx;
    sn[4 * x + 4].y = dy;
}

int main()
{
    cin >> n;  // 测试用例数量
    while (n--)
    {
        cin >> s >> t >> a >> b;  // 区域数、跨区域速度、起点区域、终点区域
        memset(sn, 0, sizeof(sn));
        memset(p, 0, sizeof(p));

        // 输入每个区域的3个顶点坐标
        for (int i = 0; i < s; i++)
        {
            for (int j = 1; j <= 3; j++)
            {
                cin >> sn[i * 4 + j].x >> sn[i * 4 + j].y;
                sn[i * 4 + j].idx = i;  // 设置区域编号
            }
            getNode(i);  // 计算第四个顶点
            sn[i * 4 + 4].idx = i;
            cin >> p[i];  // 输入区域速度
        }

        getF();  // 初始化距离矩阵

        // Floyd算法计算所有点对最短路径
        for (int k = 1; k <= 4 * s; k++)
        {
            for (int i = 1; i <= 4 * s; i++)
            {
                if (i == k) continue;
                for (int j = 1; j <= 4 * s; j++)
                {
                    if (i == j || j == k) continue;
                    f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
                }
            }
        }

        // 在起点区域和终点区域的所有顶点间寻找最短路径
        ans = 1e9;
        for (int i = 1; i <= 4; i++)
        {
            for (int j = 1; j <= 4; j++)
            {
                ans = min(ans, f[4 * a - 4 + i][4 * b - 4 + j]);
            }
        }

        printf("%.1lf\n", ans);  // 输出结果保留1位小数
    }
    return 0;
}

【运行结果】

1
3 10 1 3
1 1 1 3 3 1 30
2 5 7 4 5 2 1
8 6 8 8 11 6 3
47.5
posted @ 2026-09-01 09:25  团爸讲算法  阅读(10)  评论(0)    收藏  举报