这是NOIP最后冲刺阶段写的复习性质的博客。
这篇博客包含了Floyd算法的内容。
主要包括:
Floyd求多源最短路
Floyd求无向图最小正环
复习性质的博客。
这个东西求最短路的方式比较神奇。进行完一次 O(n3) 的算法以后,你就可以得到一个存储这结果的数组 d ,那么 d[i][j] 就是 i 到 j 的最短路长度。
这个算法同时支持有向图和无向图。
首先枚举一个中间节点 k ,然后枚举起始点 i 和终点 j ,比较 d[i][k]+d[k][j] 和 d[i][j] 的大小,把较小的一个的值赋给 d[i][j] 。即:
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
注意,在初始状态下, d 数组的处理方式:
如果 i→j 可达:
有向边:有一条边从 i 到 j 。
无向边: 有一条边连接 i 和 j 。
那么, d[i][j] 的值就是这条边的长度。
否则,它的长度就是无穷大(以下简称 INF )。
你打眼一看,这个东西似乎有说不通的地方:
我们在一开始的时候, d 数组的元素几乎都是 INF ,但是它却是作为更新的标准,这样不会导致更新的内容无效嘛?
如果你只是站在前几个 k 的取值上看,是的,这样做确实是无效的。
但是你注意看,这样的操作我们进行了 n 次,我们寻找了 n 次中间点。而且你会发现:我们在进行操作的时候,其实是三维操作( i、j、k ),而操作的对象 d 则是一个二维的对象( i、j )。因此我们可以相信它有能力将先前无效的操作抵消掉,变成有效的操作。因为每次选取一个新的中间点,这之中必定会有有效操作,而随着 k 的值越来越大,有效操作的个数也会越来越多。最终,所有操作均为有效操作。
当然,这只是一个非常笼统的解释。如果你想要更科学的解释,你往下看:
最短路径中的Floyd算法(弗洛伊德算法)的较为严格的冥想证明过程
作者:李均宇(李恒星) 2015.10.31
仍用数学归纳法,假设:
N≤n 时,弗法正确。
当 N=n+1 时,假设最新一点最后一点为 K ,此时 K=n+1 ,
三重循环中,我们都把 K 排在循环中的最后一位。
现在我们要证明的是,加上新点 K 点后,经过弗法的三重循环,原来的 n 点之间仍是最短距离,但是 n 点与 K 点之间的短离是不是最短的就不知的。
如果原来的 n 点的某两点之间最短距是与 K 点无关的,显然经过三重循环后,就是最短距了。
如果原来的 n 点的某两点之间最短距是经过 K 点的,假设 P1 , P2 , P3 ,······, Pk−1 , Pk , Pk+1 ,······, Pm 本应是实边最短距,不是虚边最短距。
那么由弗法知, P1 , P2 , P3 ,······, Pk−1 与 Pk+1 ,······, Pm 已是连通的最短路了。且 Pk−1Pk 与 PKPK+1 是原始实边,不是虚边。
经过最外层最后一次循环的松驰操作,必能连通 P1 , P2 , P3 ,······, Pk−1 , Pk , Pk+1 ,······, Pm 。
所以得证:加上新点 K 点后,经过弗法的三重循环,原来的 n 点之间仍是最短距离,但是 n 点与 K 点之间的短离是不是最短的就不知的。
由于对称性,将 K 点置入内部,把 P1 点放到最后一点,原来的循环结果不会变的,
所以三重循环后, K 点与原来的点(除 P1 外)的最短距,就可以求出来了。
由于对称性,将 K 点置入内部,把 P2 点放到最后一点,原来的循环结果不会变的,所以三重循环后, K 点与原来的点(除 P2 外,但 P1 不除外)的最短距,就可以求出来了。
所以 K 点与原来的 n 个点的最短距,也就已经求出来的了,仍是原来的三重循环也。
这样,弗法就可以较为严格的证明了。
上面已经有了,再放一次没有意义。
这个东西,GAO♂了好久才弄明白。。。QAQ
主要用途就是对于一个给定的图,求其最小环的长度(环是指从一个节点出发,在路径不重复的情况下能回到这个点的路径,最小环是指环上的路径权值和最小的环)。
那么,这个图的最小环长度就是3。
这个就比较难解释了哈。。。
首先来讲,我们先要明确一下我们到底要求什么。我们要求的是最小环的长度,那么我们只要在外面定义一个变量来记录最小环的长度就行了。
同普通的Floyd求最短路的算法是一样的,首先在最外层定义中间节点 K ,然后内层为起点和终点 i 、 j 。
由于Floyd算法具有其特殊的性质(在上面的最短路用途中进行了简单证明),因此我们应该确保最短路和最小环的求取保持一致,即同时进行。
int min_loop=INF;
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(!m[i][k]||!m[k][j]) continue;
min_loop=min(min_loop,m[i][k]+m[k][j]+d[j][i]);
}
}//找最小环
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}//找最短路
}
就像这样,不断更新,最终就成功了。。。
这个我认为它的证明跟最短路那个是一样的。。。这里不再“赘述”了。。。其实根本不会证明【滑稽】
咳,上面就是啊。。。
这里带一道裸的题来。。。《winmt长跑》(可能无权限访问)
《winmt长跑》
题目背景
winmt决定绕公园长跑了!公园中有 N 个路口,两个路口之间可能直接有道路相连,道路均为双向道路。
winmt的跑步线路只能是一个回路,不能经过同一条路两次。也就是说,winmt可以任取一处路口出发,依次经过若干个路口,最终回到起点。由于winmt实在不想跑太长的路,于是他想请你帮他求出最优的路线。
题目描述
请你帮他求出最短的跑步路线。
输入输出格式
输入格式:
输入中有多组数据。对于每组数据:
第一行有两个正整数 N, M,分别表示公园中路口的个数和道路的个数,道路均为双向道路。
以下 M 行,每行三个正整数,分别表示一条道路的两端的编号,以及这条道路的长度。 最后以 0 0 结束。
注意:可能有重边。发现重边后,一律覆盖处理,即以最后读进来的边为准。
输出格式:
对于每组数据,输出一行:
如果该回路存在,则输出一个正整数,表示该回路的总长度;否则输出”No solution.”(不要输出引号)
输入输出样例
输入样例#1:
5 6 1 4 1 3 1 10 1 2 16 2 3 100 2 5 15 5 3 20 4 3 1 2 10 1 3 20 1 4 30 0 0
输出样例#1:
61 No solution.
数据范围及约定
时空限制: 1000ms,512MB
数据规模:
对于30%的数据, n≤12;
对于100%的数据, n≤100,道路长度≤1000,数据总组数不超过10组。
标准的一眼题。直接套用模板就好了。
#include
#include
#include
using namespace std;
const int MAXN=100;
const int INF=0x3f3f3f3f;
int d[MAXN+5][MAXN+5],m[MAXN+5][MAXN+5];
int n,mm;
int main()
{
while(true)
{
scanf("%d%d",&n,&mm);
if(n==0&&mm==0) break;
for(int i=1;i<=n;i++)
{
memset(d[i],0x3f,sizeof(d[i]));
memset(m[i],0,sizeof(m[i]));
d[i][i]=0;
}
for(int i=1;i<=mm;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
d[u][v]=d[v][u]=w;
m[u][v]=m[v][u]=w;
}
int res=INF;
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
if(!m[i][k]) continue;
for(int j=i+1;j<=n;j++)
{
if(d[i][j]==INF||!m[k][j]) continue;
res=min(res,d[i][j]+m[i][k]+m[k][j]);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}
}
}
if(res==INF) cout<<"No solution."<else cout<return 0;
}
好了讲完了。什么?你不懂?这不赖我。。。。。。
【理直气壮的天使小姐姐】