摘要:
Floyd算法的应用——对于Floyd算法本质的考察
问题描述:
有0至n-1共n个村庄,每一个村庄都需要一定的时间重建,在重建完之前不得通车。求解给定时刻下,点i到点j的最短距离
原题连接洛谷P1119 灾后重建
算法分析:
提示:题目中给出的数据都是已经排好序的,包括村庄的编号按照需要重建的时间升序排序,问题中时间也是升序排序。这为阶梯提供了一个良好的思路。
Floyd算法的本质是利用动态规划的思想,通过其他的点进行中转来求的两点之间的最短路,因为我们知道,两点之间有多条路,如果换一条路可以缩短距离的话,就更新最短距离。而它最本质的思想,就是用其他的点进行中转,从而达到求出最短路的目的。
那么,如何进行中转呢?两点之间可以由一个点作为中转点更新最短路径,也可以通过多个点更新最短路径。
结合代码:
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(e[i][j]>e[i][k]+e[k][j])
e[i][j]=e[i][k]+e[k][j];
//核心代码,仅仅只有5行
这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。
(仔细理解这段话,它揭露了这个算法的本质并为本题提供了很好的方法)
到这里我们已经知道,Floyd算法就是一个利用其它点进行中转来求最短路的步骤。
而我们再回头看题意:
所有的边全部给出,按照时间顺序更新每一个可用的点(即修建好村庄),对于每个时间点进行两点之间询问,求对于目前建设的所有村庄来说任意两点之间的最短路
不正好就是Floyd算法中使用前k个节点更新最短路的思维吗?
代码以及详细注释:
#include <iostream>
#include <stdio.h>
#include <queue>
#include <vector>
#pragma warning(disable:4996)
#define INF 1000000
using namespace std;
struct question {
int s;
int dest;
int day;
question(int _s, int _dest, int _day) :s(_s), dest(_dest), day(_day) {
}
};
class Solution {
public:
int n, m;
vector<vector<int>> cost;
vector<int> time;
vector<question> ques;
vector<bool> visit;
inline void update(int k)
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
{
if (cost[i][j] > cost[i][k] + cost[k][j])
{
cost[i][j] = cost[i][k] + cost[k][j];
}
}
}
}
void out()
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
cout << cost[i][j] << " ";
cout << endl;
}
}
void Floyd()
{
cin >> n >> m;
cost.resize(n , vector<int>(n,INF));
time.resize(n, 0);
visit.resize(n + 1, false);
for (int i = 0; i < n; ++i)
{
cin>>time[i];
}
for (int i = 1; i <= m; ++i)
{
int u, v, w;
cin >> u >> v >> w;
cost[u][v] = w;
cost[v][u] = w;
}
for (int i = 0; i < n; ++i)
cost[i][i] = 0;
int Q;
cin >> Q;
for (int i = 0; i < Q; ++i)
{
int u, v, day;
cin >> u >> v >> day;
ques.push_back(question(u, v,day));
}
int k = 0;//当前遍历到第几个村庄
for (int t = 0; t < Q;++t)
{
while (k<n&&ques[t].day >= time[k])
{
visit[k] = true;
update(k);
/*out();
cout << endl << endl;*/
++k;
}
if (visit[ques[t].s]==false||visit[ques[t].dest]==false||cost[ques[t].s][ques[t].dest] == INF)
cout << "-1" << endl;
else
cout << cost[ques[t].s][ques[t].dest] << endl;
}
}
};
int main() {
//freopen("in.txt", "r", stdin);
Solution s;
s.Floyd();
return 0;
}
易错点
笔者在此题犯了一个错误,就是上述代码中的update函数。第一次写的时候在上述update函数中if语句判断条件的基础上加入了对起点i和中间j的判断。虽然题目中有明确要求,只有两个村中都完成重建后才能够通车,但是对于两个起点村庄和终点村庄是否完成重建的判断不应该加在此处,而应该加在最后的输出上。
原因如下:
虽然两个村庄并未都重建好,但是实际的公路并未受到影响。也就是两个村庄如果都修建好之后,实际的最短距离是固定不变的,倘若update函数的if语句改成
if (visit[i] && visit[j] &&cost[i][j] > cost[i][k] + cost[k][j])
{
cost[i][j] = cost[i][k] + cost[k][j];
}
那么,会导致当村庄修建好之后,实际的最短路径发生变化。比如对于输入
4 5
1 2 3 4
0 2 1
2 3 1
3 1 2
2 1 4
0 3 5
4
2 0 2
0 1 2
0 1 3
0 1 4
假设在第3天,该用村庄2作为中转点进行全局路径的更新,此时村庄3并未修好。但是如果此时不更新村庄3和村庄2之间的距离为1(cost[2][3]=cost[3][2]=1),那么当第4天到来后, 村庄1到村庄4的距离就为5,而不是4,导致出错。如图所示: