最短路算法——Floyd-Warshall(题目练习解析)

 点击 -> 算法详细讲解


 公园观景

题目描述

小明喜欢观景,于是今天他来到了公园。

已知公园有 N 个景点,景点和景点之间一共有 M 条道路。小明有 Q 个观景计划,每个计划包含一个起点 st 和一个终点 ed,表示他想从 st 去到 ed。但是小明的体力有限,对于每个计划他想走最少的路完成,你可以帮帮他吗?

输入描述

输入第一行包含三个正整数 N,M,Q

第 2 到 M + 1 行每行包含三个正整数 u,v,w 表示 uv 之间存在一条距离为 w 的路。

第 M+2 到 M + Q-1 行每行包含两个正整数 st,ed,其含义如题所述。

(1 ≤ ≤ 400,1 ≤ ≤ N×(N−1)​/2,≤ 10^3,1 ≤ u,v,st,e≤ n,1 ≤ ≤ 10^9)

输出描述

输出共 Q 行,对应输入数据中的查询。

若无法从 st 到达 ed 则输出 -1。

样例输入

3 3 3
1 2 1
1 3 5
2 3 2
1 2
1 3
2 3

样例输出

1
3
2

参考代码:

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

const long long INF = 0x3f3f3f3f3f3f3f3fLL;  //这样定义INF的好处是: INF <= INF+x
const int N = 405;
long long dp[N][N];
int n,m,q;
void input(){
    //两种初始化方法
    // for(int i = 1; i <= n; i++)
    //     for(int j = 1; j <= n; j++)
    //         dp[i][j] = INF;
    memset(dp,0x3f,sizeof(dp));
    for(int i = 1; i <= m; i++){
        int u,v;long long w;
        cin >> u >> v >> w;
        dp[u][v]=dp[v][u] = min(dp[u][v] , w);  //防止有重边
    }
}
void floyd(){
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
                dp[i][j] = min(dp[i][j] , dp[i][k] + dp[k][j]);
}
void output(){
    int s, t;
    while(q--){
        cin >> s >>t;
        if(dp[s][t]==INF) cout << "-1" <<endl;
        else if(s==t) cout << "0" <<endl;  //如果不这样,dp[i][i]不等于0
        else          cout <<dp[s][t]<<endl;
    }
}
int main(){
    cin >> n >> m >> q;
    input();
    floyd();
    output();

    return 0;
}

打印路径

题目描述

王国有 N 个城市,任意两城市间有直通的路或没有路。每条路有过路费,并且经过每个城市都要交税。定义从 a 城到 b 城,其花费为路径长度之和,再加上除 a 与 b 外所有城市的过路费之和。

现给定若干对城市,请你打印它们之间最小花费的路径。如果有多条路经符合,则输出字典序最小的路径。

输入描述

第一行给定一个 N 表示城市的数量,若 N=0 表示结束。

接下来 N 行,第 i 行有 N 个数,​ a_{i,1} \sim a_{i,n}a _{i,j } 表示第 i 个城市到第 j 个城市的直通路过路费,若 a _{i,j } ​= −1 表示没有直通路。

接下来一行有 N 个数,第 i 个数表示第 i 个城市的税。再后面有很多行,每行有两个数,表示起点和终点城市,若两个数是 -1,结束。

输出描述

对给定的每两个城市,输出最便宜的路径经过哪些点,以及最少费用。

样例输入

3
0 2 -1
2 0 5
-1 5 0
1 2 3
1 3
2 3
-1 -1
0

样例输出

From 1 to 3 :
Path: 1-->2-->3
Total cost : 9

From 2 to 3 :
Path: 2-->3
Total cost : 5

参考代码:

路径部分说明如下:
1.路径的定义。用 path[][] 记录路径,path[i][j] = u 表示起点为 i,终点为 j 的最短路径,从 i 出发下一个点是 u。一条完整的路径,是从 s 出发,查 path[s][j] = u 找到下一个点 u,然后从 u 出发,查 path[u][j] = v,下一个点是 v,等等,最后到达终点 j。

2.路径的计算。path[i][j] = path[i][k] 计算了从 i 出发的下一个点。因为 k 在 i-j 的最短路径上,所以 i-k 也是最短路径。比较 path[i][j] 和 path[i][k],它们都表示从 i 出发的下一个点,且这个点在同一个路径上,所以有 path[i][j] = path[i][k]。

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

const int INF = 0x3fffffff;
const int N = 505;
int n, mmap[N][N], tax[N], path[N][N];
void input(){
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++) {
            scanf("%d", &mmap[i][j]);
            if(mmap[i][j] == -1) mmap[i][j] = INF;
            path[i][j] = j;  //path[i][j]: 此时i、j相邻,或者断开
        }
    for(int i = 1; i <= n; i++)  scanf("%d", &tax[i]);  //税
}
void floyd(){
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++) {
                int len = mmap[i][k] + mmap[k][j] + tax[k];  //计算最短路
                if(mmap[i][j] > len) {
                    mmap[i][j] = len;
                    path[i][j] = path[i][k];  //标记到该点的前一个点
                }
                else if(len == mmap[i][j] && path[i][j] > path[i][k])
                        path[i][j] = path[i][k];  //若距离相同,按字典序
            }
}
void output(){
    int s, t;
    while(scanf("%d %d", &s, &t))    {
        if(s == -1 && t == -1) break;
        printf("From %d to %d :\n", s, t);
        printf("Path: %d", s);
        int k = s;
        while(k != t) {  //输出路径从起点直至终点
            printf("-->%d", path[k][t]);
            k = path[k][t];  //一步一步往终点走
        }
        printf("\n");
        printf("Total cost : %d\n\n", mmap[s][t]);
    }
}

int main(){
    scanf("%d", &n);
    input();
    floyd();
    output();

    return 0;
}

指数移动 

题目描述

一个图有 n 个点,有 m 个边连接这些点,边长都是 1 千米。小明的移动能力很奇怪,他一秒能跑 2^t 千米,t 是任意自然整数。问小明从点 1 到点 n,最少需要几秒。

输入描述

第一行两个整数 n,m,表示点的个数和边的个数。

接下来 m 行每行两个数字 a,b,表示一条 a 到 b 的边。

(1 ≤ ≤ 50,1 ≤ ≤ 10000,最优路径长度 ≤ 2^32。)

输出描述

输出一个整数,表示答案。

样例输入

5 5
1 2
2 2
3 4
3 5
2 3

样例输出

1

 参考代码:

这道题的路径在 “兜圈子”,某些点可以经过多次。
        小明所跑的路径,可以分成几段,每一段长为 2^t,所以关键在于确定任意点对 (i, j) 点之间是否存在 2^t 的路径。由于要计算所有点对之间的路径,所以用 Floyd 算法是合适的。计算出一个新图,若点对 (i, j) 之间的存在长 2^t 的路径,把 (i, j) 的边长 mp[i][j] 赋值为 1 秒,否则为无穷大。在这个新图上,求 1 到 n 的最短路径就是答案。
        计算长度为 2^t 的路径:可以根据倍增的原理,有 2^t = 2^{t-1} +2^{t-1}。用 p[i][j][t] = true 表示 i 、 j 之间有一条长 2^t 的路径,根据 Floyd 算法的思路,路径通过一个中转点 k,有 p[i][j][t]= p[i][k][t-1]+ p[k][j][t-1]。利用倍增原理计算新图 mp[],复杂度 O(n^3)。在新图 mp[] 上计算最短路,用任何最短路算法都行,这里就用最简单的 Floyd 算法。

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

const int N = 55;
bool p[N][N][34];
int mp[N][N];
int main(){
    memset(mp,0x3f,sizeof(mp));
    int n,m;   cin >> n >> m;
    for( int i = 1;i <= m; i++){
        int u,v;  cin >> u >> v;
        mp[u][v] = 1;
        p[u][v][0] = true;
    }
    for(int t = 1;t <= 32; t++)  //长度为2^t的路径
        for(int k = 1;k <= n; k++)  //Floyd
            for(int i = 1;i <= n; i++)
                for(int j = 1;j <= n; j++)
                    if(p[i][k][t - 1] == true && p[k][j][t - 1] == true){
                        p[i][j][t] = true;
                        mp[i][j] = 1;  //计算得到新图
                    }
    for(int k = 1;k <= n; k++)  //求最短路,就用Floyd
        for(int i = 1;i <= n; i++)
            for(int j = 1;j <= n; j++)
                mp[i][j] = min(mp[i][j],mp[i][k] + mp[k][j]);
    cout << mp[1][n] << endl;

    return 0;
}

如有错误和需要改进完善之处,欢迎大家纠正指教。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值