题解:洛谷 P1744 采购特价商品

【题目来源】

洛谷: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

【核心思想】

  1. 问题分析:给定 \(n\) 家店的平面坐标 \((x_i, y_i)\)\(m\) 条无向通路,每条通路的边权为两点间的欧几里得距离。求从起点 \(s\) 到终点 \(t\) 的最短路径长度。这是一个多源最短路径问题,由于 \(n \leq 100\) 规模较小,适合使用 Floyd-Warshall 算法直接求出所有点对之间的最短距离。

  2. 算法选择

    • Floyd-Warshall 算法:通过动态规划思想,枚举中间点 \(k\) 逐步松弛所有点对之间的最短距离
    • 欧几里得距离:直接通路的边权由坐标通过 \(\sqrt{(x_i-x_j)^2 + (y_i-y_j)^2}\) 计算
  3. 关键步骤

    • 初始化:读取 \(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]\),保留两位小数
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^3)\),三重循环,\(n \leq 100\) 时完全可接受
    • 空间复杂度:\(O(n^2)\),存储 \(dp\) 距离矩阵
  5. 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
posted @ 2026-08-10 12:27  团爸讲算法  阅读(15)  评论(0)    收藏  举报