昨天的模拟赛中有一道用到最短路算法的题,自己竟然写T了,所以今天来温习一下三个最短路算法,把模板写一写。
首先说明,这三个算法都是无向图有向图皆适用的。
Floyd算法: 三个里面最好写的算法,算法原理是通过枚举中间点k,不断对两点之间的最短路长度进行松弛。d[i][j]表示i到j之间的最短路长度,d[i][j] = min{d[i][k] + d[k][j] | k ∈ [1, n]}。最终算法可以求出任意两点之间的最短路。由松弛表达式也可以看出,此算法要求保存图的方式是邻接矩阵:d[i][j]表示i到j之间一条边的长度(无边则为无穷大)。算法复杂度为O(n^3)。
模板:
void Floyd(){
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]);
值得注意的是:最外层循环是枚举中间点k,否则会出错,可以手推模拟一下证明。
Floyd传递闭包是一个很常见的用法,传递闭包只是用Floyd传递一种关系,比如连通性,可达关系。
[例题:POJ 3660](http://poj.org/problem?id=3660)
题意:给定n,m,表示有n头奶牛以及接下来输入的m个胜负关系,每组胜负关系形式为a,b,表示a可以战胜b,问有多少头奶牛的名次是可以确定的。
思路:当已知一个奶牛被i头战胜,又战胜了j头奶牛且i+j == n-1时,它的名次就是可以确定的,我们只需要知道这头奶牛是否被另一头奶牛战胜或是否战胜另一头奶牛即可。我们可以把每一个胜负关系看作有向边,使用Floyd传递闭包即可。
#include
int n, m, ans, sure;
bool d[101][101];
int main()
{
scanf("%d %d", &n, &m);
for(int a, b, i(1); i <= m; i++){
scanf("%d %d", &a, &b);
d[a][b] = 1;
}
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(d[i][k] && d[k][j]) d[i][j] = 1;
for(int i = 1; i <= n; i++){
sure = 0;
for(int j = 1; j <= n; j++){
if(d[j][i]) sure++;
if(d[i][j]) sure++;
}
ans += sure == n-1;
}
printf("%d", ans);
}
vector G[M];
struct edge{
int u, v, w;
}E[M*M];
bool SPFA(){
memset(d, 0x7f, sizeof d);
d[s] = 0;
q.push(s);
times[s] = inq[s] = 1;
while(q.size()){
int u = q.front();
q.pop();
inq[u] = 0;
for(int i = 0; i < G[i].size(); i++){
int e = G[u][i], v = E[e].v, w = E[e].w;
if(d[v] > d[u]+w){
d[v] = d[u]+w;
if(!inq[v]){
q.push(v);
inq[v] = 1;
times[v]++;
if(times[v] == n) return 0;
}
}
}
}
return 1;
}
struct node{
int d, num;
bool operator < (node i)const{
return d > i.d;
}
}t;
priority_queue q;
void Dijkstra(){
memset(d, 0x7f, sizeof d);
d[s] = 0;
t.num = s, t.d = 0;
q.push(t);
while(q.size()){
t = q.top(); q.pop();
int u = t.num;
if(vis[u]) continue;
vis[u] = 1;
for(int j = 0; j < G[u].size(); j++){
int e = G[u][j], v = E[e].v, w = E[e].w;
if(d[v] > d[u]+w) {
d[v] = d[u] + w;
t.num = v, t.d = d[v];
q.push(t);
}
}
}
}