题解:洛谷 P1027 Car的旅行路线
【题目来源】
洛谷:P1027 [NOIP 2001 提高组] Car 的旅行路线 - 洛谷
【题目描述】
又到暑假了,住在城市 A 的 Car 想和朋友一起去城市旅游。
她知道每个城市都有 \(4\) 个飞机场,分别位于一个矩形的 \(4\) 个顶点上,同一个城市中两个机场之间有一条笔直的高速铁路,第 \(i\) 个城市中高速铁路的单位里程价格为 \(T_i\),任意两个不同城市的机场之间均有航线,所有航线单位里程的价格均为 \(t\)。

注意:图中并没有标出所有的铁路与航线。
那么 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
【核心思想】
-
问题分析:有 \(S\) 个城市,每个城市有 4 个机场(位于矩形的四个顶点)。同一城市内机场之间通过高速铁路连接,费用为 \(T_i \times\) 距离;不同城市之间通过航线连接,费用为 \(t \times\) 距离。需要从城市 A 到城市 B,选择任意机场作为起点和终点,求最小总费用。本质是一个多源最短路问题,将每个机场视为图中的节点,共 \(4S\) 个节点,用 Floyd 算法或 Dijkstra 求最短路。
-
算法选择:
- Floyd 算法:由于总节点数最多 \(4 \times 100 = 400\)(本题 S 未明确但通常不大),Floyd 的 \(O((4S)^3)\) 完全可行,且实现简单。
- 建图:每个城市给 4 个机场编号。已知 3 个顶点,利用矩形性质(对角线互相平分且相等)求第 4 个顶点。同一城市内 4 个机场两两连边,权值 = 距离 \(\times T_i\);不同城市之间所有机场两两连边,权值 = 距离 \(\times t\)。
- 答案:城市 A 的 4 个机场到城市 B 的 4 个机场的最短距离的最小值。
-
关键步骤:
- 读入数据:对于每组数据,读入 \(S, t, A, B\),以及每个城市的 3 个顶点坐标和铁路单价 \(T_i\)。
- 计算第 4 个顶点:利用矩形的对角线互相平分且相等:已知三个顶点,判断哪个是直角顶点(通过距离平方和勾股定理),然后计算第四个顶点坐标。
- 建图:初始化距离矩阵
f[i][j],同城市内按铁路单价,不同城市按飞机单价,自环为 0。 - Floyd:计算所有点对之间的最短路。
- 求答案:在 A 城市的 4 个机场和 B 城市的 4 个机场之间取最小距离。
-
时间/空间复杂度:
- 时间复杂度:\(O((4S)^3)\),\(S\) 通常不超过 100,可接受。
- 空间复杂度:\(O((4S)^2)\)。
-
几何 + 最短路:
- 矩形顶点补全:已知矩形三个顶点,利用向量关系或勾股定理求第四个顶点。
- 图论建模:将机场作为节点,费用作为边权,问题转化为标准的最短路问题。
- 适用场景:这类“多节点、多模式交通”问题,通常建模为图论最短路。
【算法标签】
普及+ #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
浙公网安备 33010602011771号