题解:洛谷 P1744 采购特价商品
【题目来源】
【题目描述】
中山路店山店海,成了购物狂爱与愁大神的“不归之路”。中山路上有 \(n\)(\(n \leq 100\))家店,每家店的坐标均在 \(-10000\) 至 \(10000\) 之间。其中的 \(m\) 家店之间有通路。若有通路,则表示可以从一家店走到另一家店,通路的距离为两点间的直线距离。现在爱与愁大神要找出从一家店到另一家店之间的最短距离。你能帮爱与愁大神算出吗?
【输入】
共 \(n+m+3\) 行:
第一行:整数 \(n\)。
接下来 \(n\) 行:每行两个整数 \(x\) 和 \(y\),描述了一家店的坐标。
接下来一行:整数 \(m\)。
接下来 \(m\) 行:每行描述一条通路,由两个整数 \(i\) 和 \(j\) 组成,表示第 \(i\) 家店和第 \(j\) 家店之间有通路。
接下来一行:两个整数 \(s\) 和 \(t\),分别表示原点和目标店。
【输出】
仅一行:一个实数(保留两位小数),表示从 \(s\) 到 \(t\) 的最短路径长度。
【输入样例】
5
0 0
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5
【输出样例】
3.41
【核心思想】
-
问题分析:给定 \(n\) 家店的平面坐标 \((x_i, y_i)\) 和 \(m\) 条无向通路,每条通路的边权为两点间的欧几里得距离。求从起点 \(s\) 到终点 \(t\) 的最短路径长度。这是一个多源最短路径问题,由于 \(n \leq 100\) 规模较小,适合使用 Floyd-Warshall 算法直接求出所有点对之间的最短距离。
-
算法选择:
- Floyd-Warshall 算法:通过动态规划思想,枚举中间点 \(k\) 逐步松弛所有点对之间的最短距离
- 欧几里得距离:直接通路的边权由坐标通过 \(\sqrt{(x_i-x_j)^2 + (y_i-y_j)^2}\) 计算
-
关键步骤:
- 初始化:读取 \(n\)(店铺数)、\(n\) 个坐标 \((x_i, y_i)\)、\(m\)(通路数)
- 建图:读取 \(m\) 条通路 \((u, v)\),建立无向邻接矩阵 \(g[u][v] = g[v][u] = 1\)
- 初始化距离矩阵 \(dp[i][j]\):
- \(dp[i][i] = 0\)(自己到自己为 \(0\))
- 若 \(g[i][j] = 1\),则 \(dp[i][j] = calc(i, j)\)(欧几里得距离)
- 其余 \(dp[i][j] = \infty\)(不可达)
- Floyd 三重循环松弛:
- 枚举中间点 \(k\)(\(1\) 到 \(n\))
- 枚举起点 \(i\)(\(1\) 到 \(n\))
- 枚举终点 \(j\)(\(1\) 到 \(n\))
- 松弛操作:若 \(dp[i][j] > dp[i][k] + dp[k][j]\),则更新 \(dp[i][j] = dp[i][k] + dp[k][j]\)
- 输出答案:\(dp[s][t]\),保留两位小数
-
时间/空间复杂度:
- 时间复杂度:\(O(n^3)\),三重循环,\(n \leq 100\) 时完全可接受
- 空间复杂度:\(O(n^2)\),存储 \(dp\) 距离矩阵
-
Floyd 的核心思想:
- 动态规划思想:\(dp[k][i][j]\) 表示仅使用前 \(k\) 个节点作为中间点时 \(i\) 到 \(j\) 的最短距离,通过滚动数组优化为 \(dp[i][j]\)
- 松弛原理:最短路径的子路径也是最短路径,若 \(i \to j\) 经过 \(k\) 更短,则更新
- 全源最短路径:一次计算即可得到任意两点间的最短距离,适合频繁查询场景
- 适用场景:节点数 \(n \leq 500\) 的稠密图全源最短路径问题,或需要多次查询不同起点终点的场景
【算法标签】
普及 #Floyd
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 105; // 最大店铺数量
int n, m, st, ed; // n: 店铺数量, m: 通路数量, st: 起点, ed: 终点
int x[N], y[N]; // x[i], y[i]: 第i家店的坐标
double dp[N][N]; // dp[i][j]: 从店铺i到店铺j的最短距离(Floyd算法)
int g[N][N]; // g[i][j]: 邻接矩阵,g[i][j]=1表示i和j之间有通路
// 计算两点之间的欧几里得距离(直线距离)
double calc(int a, int b)
{
return sqrt((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]));
}
int main()
{
cin >> n; // 读入店铺数量
// 读入每家店的坐标
for (int i = 1; i <= n; i++)
cin >> x[i] >> y[i];
cin >> m; // 读入通路数量
// 读入m条通路
while (m--)
{
int u, v;
cin >> u >> v;
g[u][v] = 1; // u和v之间有通路
g[v][u] = 1; // 无向图,双向建边
}
cin >> st >> ed; // 读入起点和终点
// 初始化Floyd距离矩阵
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
dp[i][j] = 1e9; // 初始化为无穷大(表示不可达)
// 自己到自己的距离为0
for (int i = 1; i <= n; i++)
dp[i][i] = 0;
// 对于直接有通路的店铺,初始化距离为两点间的直线距离
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (g[i][j] == 1)
dp[i][j] = calc(i, j);
// ========== Floyd-Warshall算法:求任意两点间的最短路径 ==========
// 枚举中间点k
for (int k = 1; k <= n; k++)
{
// 枚举起点i
for (int i = 1; i <= n; i++)
{
// 枚举终点j
for (int j = 1; j <= n; j++)
{
// 松弛操作:如果经过k点能缩短i到j的距离,则更新
if (dp[i][j] > dp[i][k] + dp[k][j])
{
dp[i][j] = dp[i][k] + dp[k][j];
}
}
}
}
// 输出从起点st到终点ed的最短距离,保留两位小数
printf("%.2lf", dp[st][ed]);
return 0;
}
【运行结果】
5
0 0
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5
3.41
浙公网安备 33010602011771号