一.最短路的常用算法
f
l
o
y
e
d
:
O
(
n
3
)
floyed:O(n^3)
f l o y e d : O ( n 3 ) 经典的多源最短路算法,基本思想为动态规划 ,可用于负权边
s
p
f
a
:
O
(
k
m
)
spfa:O(km)
s p f a : O ( k m ) 单源最短路算法,基本思想为广度优先搜索 使用与负权边,缺点易被特殊数据卡时间复杂度
d
i
j
k
s
t
r
a
:
O
(
n
l
o
g
n
)
dijkstra:O(nlogn)
d i j k s t r a : O ( n l o g n ) 经典单源最短路算法,基本思想为广度优先搜索 不适用于负权边,时间复杂度优于spfa的原因是其每个点只入堆一次,vis只变1,不变0
# include <bits/stdc++.h>
using namespace std;
const int N= 1e5 , M= 1e6 ;
struct E {
int u, v, c, next;
} e[ M* 2 ] ;
int vex[ N] , k, que[ M* 4 ] , vis[ N] , dis[ N] , head, rear;
int n;
void add ( int u, int v, int c) {
k++ ;
e[ k] . v= v;
e[ k] . u= u;
e[ k] . c= c;
e[ k] . next= vex[ u] ;
vex[ u] = k;
return ;
}
priority_queue< pair< int , int > , vector< pair< int , int >> , greater< pair< int , int > > > q;
void Dij ( ) {
for ( int i= 1 ; i<= n* ; i++ ) dis[ i] = INT32_MAX;
dis[ 1 ] = 0 ;
q. push ( make_pair ( dis[ 1 ] , 1 ) ) ;
vis[ 1 ] = 1 ;
while ( ! q. empty ( ) ) {
int u= q. top ( ) . second;
q. pop ( ) ;
vis[ u] = 1 ;
for ( int i= vex[ u] ; i; i= e[ i] . next) {
int v= e[ i] . v;
if ( dis[ v] > dis[ u] + e[ i] . w) {
dis[ v] = dis[ u] + e[ i] . w;
q. push ( make_pair ( dis[ v] , v) ) ;
}
}
}
for ( int i= 1 ; i<= n; i++ ) {
if ( vis[ i] == 0 ) dis[ i] = - 1 ;
cout<< dis[ i] << " " ;
}
}
void spfa ( int s, int t) {
for ( int i= 1 ; i<= n; i++ ) dis[ i] = 1e9 ;
dis[ s] = 0 ;
que[ rear++ ] = s;
while ( head< rear) {
int u= que[ head] ;
vis[ u] = 0 ;
for ( int i= vex[ u] ; i; i= e[ i] . next) {
int v= e[ i] . v;
if ( dis[ v] > dis[ u] + e[ i] . c) {
dis[ v] = dis[ u] + e[ i] . c;
if ( ! vis[ v] ) {
que[ rear++ ] = v;
vis[ v] = 1 ;
}
}
}
head++ ;
}
cout<< dis[ t] ;
}
void floyed ( int s, int t) {
for ( int i= 1 ; i<= n; i++ ) {
for ( int j= 1 ; j<= n; j++ ) {
if ( i!= j) dis[ i] [ j] = 1e9 ;
else dis[ i] [ j] = 0 ;
}
}
for : dis[ u] [ v] = c
for ( int k= 1 ; k<= n; k++ ) {
for ( int i= 1 ; i<= n; i++ ) {
for ( int j= 1 ; j<= n; j++ ) {
dis[ i] [ j] = min ( dis[ i] [ k] + dis[ k] [ j] ) ;
}
}
}
cout<< dis[ s] [ t] ;
}
int main ( ) {
int m, s, t, u, v, c;
cin>> n>> m>> s>> t;
for ( int i= 1 ; i<= m; i++ ) {
cin>> u>> v>> c;
add ( u, v, c) ;
add ( v, u, c) ;
}
dj ( s, t) ;
}
二.单源最短路例题
例题1:区间dp+单源最短路
例题链接
题目描述: m 个点构成一张无向图,有边权表示距离。起点为 1 ,终点为 m 。d 个限制
(
u
,
s
,
t
)
(u,s,t)
( u , s , t ) 表示点
u
u
u 不能在
s
s
s 到
t
t
t 天中的任意一天使用。n 天,每天都要从起点 1 走到终点 m 一次。如果更改路线,则需要增加 p 的距离。求完成这 n 次从 1 到 n 的最短路,至少距离走多少的总距离。例如:前两天走的路径是一种,后两天走的路径是另一种,那么其总距离为:四条路径之和 + p 。
n
∈
[
1
,
100
]
,
m
∈
[
1
,
20
]
n\in[1,100],m\in[1,20]
n ∈ [ 1 , 1 0 0 ] , m ∈ [ 1 , 2 0 ] 状态设置:
f
[
l
]
[
r
]
f[l][r]
f [ l ] [ r ] 表示完成第 l 天到第 r 天,一共需要的最小距离。转移方程:
f
[
l
]
[
r
]
=
m
i
n
(
f
[
l
]
[
r
]
,
f
[
l
]
[
k
]
+
f
[
k
+
1
]
[
r
]
+
p
)
,
k
∈
[
l
,
r
)
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+p),k\in[l,r)
f [ l ] [ r ] = m i n ( f [ l ] [ r ] , f [ l ] [ k ] + f [ k + 1 ] [ r ] + p ) , k ∈ [ l , r ) 初始值:
f
[
l
]
[
r
]
=
s
p
f
a
(
l
,
r
)
f[l][r]=spfa(l,r)
f [ l ] [ r ] = s p f a ( l , r )
# include <bits/stdc++.h>
using namespace std;
const int N= 105 , M= 1e5 + 10 ;
struct E {
int u, v, w, next;
} e[ M] ;
int vex[ N] , vis[ N] , tot, lim[ N] , ban[ N] [ N] , m;
long long dis[ N] , f[ N] [ N] ;
void add ( int u, int v, int w) {
tot++ ;
e[ tot] . u= u;
e[ tot] . v= v;
e[ tot] . w= w;
e[ tot] . next= vex[ u] ;
vex[ u] = tot;
}
long long spfa ( int s, int t) {
for ( int i= 1 ; i<= m; i++ ) {
vis[ i] = 0 ;
dis[ i] = 1e16 ;
lim[ i] = 0 ;
}
dis[ 1 ] = 0 ;
for ( int i= 1 ; i<= m; i++ ) {
for ( int j= s; j<= t; j++ ) {
if ( ban[ i] [ j] ) lim[ i] = 1 ;
}
}
queue< int > q;
if ( lim[ 1 ] == 0 ) q. push ( 1 ) ;
while ( ! q. empty ( ) ) {
int u= q. front ( ) ;
vis[ u] = 0 ;
for ( int i= vex[ u] ; i; i= e[ i] . next) {
int v= e[ i] . v;
if ( lim[ v] ) continue ;
if ( dis[ v] > dis[ u] + e[ i] . w) {
dis[ v] = dis[ u] + e[ i] . w;
if ( vis[ v] == 0 ) {
q. push ( v) ;
vis[ v] = 1 ;
}
}
}
q. pop ( ) ;
}
return dis[ m] * ( t- s+ 1 ) ;
}
int main ( ) {
int n, p, e, u, v, w, d, s, t;
cin>> n>> m>> p>> e;
for ( int i= 1 ; i<= e; i++ ) {
cin>> u>> v>> w;
add ( u, v, w) ;
add ( v, u, w) ;
}
cin>> d;
for ( int i= 1 ; i<= d; i++ ) {
cin>> u>> s>> t;
for ( int j= s; j<= t; j++ ) {
ban[ u] [ j] = 1 ;
}
}
for ( int i= 1 ; i<= n; i++ ) f[ i] [ i] = spfa ( i, i) ;
for ( int len= 2 ; len<= n; len++ ) {
for ( int l= 1 , r= l+ len- 1 ; r<= n; l++ , r++ ) {
f[ l] [ r] = spfa ( l, r) ;
for ( int k= l; k< r; k++ ) f[ l] [ r] = min ( f[ l] [ r] , f[ l] [ k] + f[ k+ 1 ] [ r] + p) ;
}
}
cout<< f[ 1 ] [ n] ;
}
三.floyd 的深入学习
随着时间新开点
前言
floyd的算法精髓,其实就是通过中转点的依次增多,来松弛这张完全图的所有多源最短路的最短路径。 那么floyd就会很适合这样的一个问题:点最初关闭,随着时间的推移依次允许使用。那么我们以时间为顺序,依次根据这些开启的点作为中专点,去松弛最短路径。
例题1:随着时间新开点
题目描述: n 个点,m 条边,构成一张无向连通图。每个点在第
t
i
t_i
t i 天被建造,只有建造后的点才能通过。q个询问,每次查询 u 到 v 的第 t 天的最短路。保证 t 递增,
t
i
t_i
t i 也递增。问题分析: 很显然,我们可以通过当前询问的天数,去开启新的中介点,用这些中介点去松弛最短路径。
while ( q-- ) {
cin>> x>> y>> t;
for ( k= k; tim[ k] <= t&& k< n; k++ ) {
for ( int i= 1 ; i<= n; i++ ) {
for ( int j= 1 ; j<= n; j++ ) {
g[ i] [ j] = min ( g[ i] [ j] , g[ i] [ k] + g[ k] [ j] ) ;
}
}
}
if ( t< max ( tim[ x] , tim[ y] ) ) cout<< - 1 << endl;
else if ( g[ x] [ y] == 1e9 ) cout<< - 1 << endl;
else cout<< g[ x] [ y] << endl;
}
例题2:随着
w
i
w_i
w i 新开点
题目描述: n 个点,m 条边,构成一张无向连通图。每个点有一个
w
i
w_i
w i 值。q 个询问每次查询 u 到 v 的最短路径,且该路径不能经过
w
i
>
w
u
w_i>w_u
w i > w u 的点。问题分析: 我们对 q 次询问的
w
i
w_i
w i 从小到大排序,依次对询问解决。那么 q 增大的过程,其实就是中介点依次开放的过程。与例题1异曲同工。
floyd求路径数量(运用floyd思想,本质是矩阵ksm)
前言
已知图的邻接矩阵为
A
n
n
A_{nn}
A n n (其中
A
[
i
]
[
j
]
=
1
/
0
A[i][j]=1/0
A [ i ] [ j ] = 1 / 0 表示
i
i
i 到
j
j
j 是否有路径)。根据
f
l
o
y
d
floyd
f l o y d 的思想,
A
n
n
k
A_{nn}^k
A n n k 中
A
[
i
]
[
j
]
A[i][j]
A [ i ] [ j ] 就是
i
i
i 走
k
k
k 步到
j
j
j ,一共有多少种方案。
例题1:求起点1走 t 步后的方案总数
题目描述: n 个点,m 条无向边。起点为 1 。每步他可能有三次操作:向其连边的点走去;原地不动;自爆。
n
∈
[
1
,
30
]
,
m
∈
[
1
,
100
]
,
t
∈
[
1
,
1
e
6
]
n\in[1,30],m\in[1,100],t\in[1,1e6]
n ∈ [ 1 , 3 0 ] , m ∈ [ 1 , 1 0 0 ] , t ∈ [ 1 , 1 e 6 ] 问题分析:
A
n
n
t
A_{nn}^t
A n n t 中的
A
[
i
]
[
j
]
A[i][j]
A [ i ] [ j ] 即为
i
i
i 走
t
t
t 步到
j
j
j 的方案数。那么答案就是
∑
i
=
1
n
A
[
1
]
[
i
]
\sum_{i=1}^n A[1][i]
∑ i = 1 n A [ 1 ] [ i ] 。原地不动的情况:
A
[
i
]
[
i
]
=
1
,
i
∈
[
1
,
n
]
A[i][i]=1,i\in[1,n]
A [ i ] [ i ] = 1 , i ∈ [ 1 , n ] 自爆:
A
[
i
]
[
0
]
=
1
,
i
∈
[
1
,
n
]
A[i][0]=1,i\in[1,n]
A [ i ] [ 0 ] = 1 , i ∈ [ 1 , n ] ,这样走到 0 就走不出来了 注意:
A
[
0
]
[
0
]
=
1
A[0][0]=1
A [ 0 ] [ 0 ] = 1
# include <bits/stdc++.h>
using namespace std;
const int N= 32 ;
long long n, m, t, mod= 2017 ;
struct Matrix {
long long a[ N+ 1 ] [ N+ 1 ] ;
Matrix ( ) {
for ( int i= 1 ; i<= N; i++ ) a[ i] [ i] = 1 ;
}
Matrix operator * ( const Matrix & obj) {
Matrix ret;
for ( int i= 0 ; i<= n; i++ ) {
for ( int j= 0 ; j<= n; j++ ) {
ret. a[ i] [ j] = 0 ;
for ( int k= 0 ; k<= n; k++ ) {
ret. a[ i] [ j] = ( ret. a[ i] [ j] + a[ i] [ k] * obj. a[ k] [ j] ) % mod;
}
}
}
return ret;
}
} a, ans;
void ksm ( long long b) {
while ( b) {
if ( b& 1 ) ans= ans* a;
a= a* a;
b= b>> 1 ;
}
}
int main ( ) {
int u, v;
cin>> n>> m;
for ( int i= 1 ; i<= m; i++ ) {
cin>> u>> v;
a. a[ u] [ v] = a. a[ v] [ u] = 1 ;
}
cin>> t;
for ( int i= 1 ; i<= n; i++ ) a. a[ i] [ 0 ] = 1 ;
for ( int i= 1 ; i<= n; i++ ) a. a[ i] [ i] = 1 ;
ksm ( t) ;
long long sum= 0 ;
for ( int i= 0 ; i<= n; i++ ) sum= ( sum+ ans. a[ 1 ] [ i] ) % mod;
cout<< sum;
return 0 ;
}
floyd判最小环
例题链接
题目描述: 给定一张无向图,求图中一个至少包含 3 个点的环,环上的节点不重复,并且环上的边的长度之和最小,求最小权值和。问题分析: 使用 Floyd 算法
O
(
n
3
)
O(n^3)
O ( n 3 ) 求最小环。Floyd 在枚举第 k 个点作为中介点时,已经求出了只有前 k-1 个点的最短路。枚举每对 u,v,已知 u,v 的最短路,则 u 再通过 k 到达 v 就可以构成一个环,求这个环的最小值即可。
# include <bits/stdc++.h>
using namespace std;
long long mp[ 105 ] [ 105 ] , f[ 105 ] [ 105 ] ;
int main ( ) {
int n, m, u, v, c;
cin>> n>> m;
for ( int i= 1 ; i<= n; i++ ) {
for ( int j= 1 ; j<= n; j++ ) {
mp[ i] [ j] = 1e14 ;
f[ i] [ j] = 1e14 ;
}
}
for ( int i= 1 ; i<= m; i++ ) {
cin>> u>> v>> c;
mp[ u] [ v] = c;
mp[ v] [ u] = c;
f[ u] [ v] = c;
f[ v] [ u] = c;
}
long long ans= 1e14 ;
for ( int k= 1 ; k<= n; k++ ) {
for ( int i= 1 ; i<= n; i++ ) {
for ( int j= i+ 1 ; j<= n; j++ ) {
ans= min ( ans, f[ i] [ j] + mp[ i] [ k] + mp[ k] [ j] ) ;
}
}
for ( int i= 1 ; i<= n; i++ ) {
for ( int j= 1 ; j<= n; j++ ) {
f[ i] [ j] = min ( f[ i] [ j] , f[ i] [ k] + f[ k] [ j] ) ;
}
}
}
if ( ans> 1e10 ) cout<< "No solution." ;
else cout<< ans;
return 0 ;
}
四.SPFA的深入学习
最短路计数
如果从 u 到 v 的路径和原来到 v 的最短路径一致,则通向 u 的最短路均为通向 v 的最短路,到 v 的最短路累加上到 u 的最短路即可。 如果从 u 到 v 的路径更近,则通向 v 的最短路等于通向 u 的最短路
void spfa ( int s) {
for ( int i= 1 ; i<= n; i++ ) dis[ i] = 1e9 ;
dis[ s] = 0 ;
num[ s] = 1 ;
q. push ( s) ;
while ( ! q. empty ( ) ) {
int u= q. top ( ) ;
vis[ u] = 0 ;
for ( int i= vex[ u] ; i; i= e[ i] . next) {
v= e[ i] . v;
if ( dis[ v] > dis[ u] + e[ i] . c) {
dis[ v] = dis[ u] + e[ i] . c;
num[ v] = num[ u] ;
if ( ! vis[ v] ) {
vis[ v] = 1 ;
q. push ( v) ;
}
} else if ( dis[ v] == dis[ u] + e[ i] . c) num[ v] += num[ u] ;
}
q. pop ( ) ;
}
}
spfa判负环
要知道,dij是不能跑负权边的。因此判负环只能用 spfa 如果存在负环,该图是没有最短路的,并且 bfs 会进入死循环 原理: 如果图中不存在负权环,则从 i 到每个点的最短路经过的点数均不会大于n。因此,只需要在 spfa 的过程中判断最短路经过的点数是否大于 n 即可判断是否有负权环。 注意: 由于图不一定连通,若要判从某个点出发是否可能经过负环只需将该点入队即可;若要判整张图中是否有负环只需将所有点入队即可。
int spfa ( int s) {
for ( int i= 1 ; i<= n; i++ ) dis[ i] = 1e9 ;
for ( int i= 1 ; i<= n; i++ ) vis[ i] = 0 ;
for ( int i= 1 ; i<= n; i++ ) cnt[ i] = 0 ;
dis[ s] = 0 ;
cnt[ s] = 1 ;
queue< int > q;
q. push ( s) ;
while ( ! q. empty ( ) ) {
int u= q. front ( ) ;
vis[ u] = 0 ;
for ( int i= vex[ u] ; i; i= e[ i] . next) {
int v= e[ i] . v;
if ( dis[ v] > dis[ u] + e[ i] . c) {
dis[ v] = dis[ u] + e[ i] . c;
cnt[ v] = cnt[ u] + 1 ;
if ( cnt[ v] > n) return 1 ;
if ( ! vis[ v] ) {
vis[ v] = 1 ;
q. push ( v) ;
}
}
}
q. pop ( ) ;
}
return 0 ;
}
五.其他例题
构造非负权图,使用 Dij
例题链接
题目描述: n个点;
a
a
a 条无向边,边权非负;
b
b
b 条有向边,边权可能为负,但是保证:若该条边是
u
u
u 到
v
v
v 的,则
v
v
v 到
u
u
u 没有通路。球起点
s
s
s 到所有点的距离。
n
<
=
2
e
4
,
a
,
b
<
=
5
e
4
n<=2e4,a,b<=5e4
n < = 2 e 4 , a , b < = 5 e 4 。问题分析: 卡普通
s
p
f
a
spfa
s p f a ,但是优化的
s
p
f
a
spfa
s p f a 仍然能过去,但是这里不用这个方法。显然,这
a
+
b
a+b
a + b 条路径可以变成一张缩完点后:点内无负权边;点间有负权边的图。可以在点内
D
i
j
Dij
D i j ,点外拓扑序即可。如何构建拓扑序:先建完无向边,缩点;再连有向边添加度。
# include <bits/stdc++.h>
using namespace std;
const int N= 2e6 + 10 ;
# define inf 1000000000
struct E {
int u, v, w, next;
} e[ N* 2 ] ;
int vex[ N] , color[ N] , co, k, in[ N] , vis[ N] ;
long long dis[ N] ;
vector< int > node[ N] ;
vector< pair< int , int > > G[ N] ;
void add ( int u, int v, int w) {
k++ ;
e[ k] . u= u;
e[ k] . v= v;
e[ k] . w= w;
e[ k] . next= vex[ u] ;
vex[ u] = k;
}
void dfs ( int u) {
color[ u] = co;
node[ co] . push_back ( u) ;
for ( int i= vex[ u] ; i; i= e[ i] . next) {
int v= e[ i] . v;
if ( color[ v] == 0 ) dfs ( v) ;
}
}
int main ( ) {
int n, a, b, s, u, w, v;
cin>> n>> a>> b>> s;
for ( int i= 1 ; i<= a; i++ ) {
cin>> u>> v>> w;
add ( u, v, w) ;
add ( v, u, w) ;
}
for ( int i= 1 ; i<= n; i++ ) {
if ( color[ i] == 0 ) {
co++ ;
dfs ( i) ;
}
dis[ i] = inf;
}
dis[ s] = 0 ;
for ( int i= 1 ; i<= b; i++ ) {
cin>> u>> v>> w;
G[ u] . push_back ( make_pair ( v, w) ) ;
in[ color[ v] ] ++ ;
}
queue< int > q;
for ( int i= 1 ; i<= co; i++ ) {
if ( in[ i] == 0 ) q. push ( i) ;
}
while ( ! q. empty ( ) ) {
priority_queue< pair< int , int > , vector< pair< int , int > > , greater< pair< int , int > > > dij;
int u= q. front ( ) ;
q. pop ( ) ;
for ( int i= 0 ; i< node[ u] . size ( ) ; i++ ) {
int v= node[ u] [ i] ;
if ( dis[ v] < inf) dij. push ( make_pair ( dis[ v] , v) ) ;
}
while ( ! dij. empty ( ) ) {
int u= dij. top ( ) . second;
dij. pop ( ) ;
if ( vis[ u] ) continue ;
vis[ u] = 1 ;
for ( int i= vex[ u] ; i; i= e[ i] . next) {
int v= e[ i] . v;
if ( dis[ v] > dis[ u] + e[ i] . w) {
dis[ v] = dis[ u] + e[ i] . w;
dij. push ( make_pair ( dis[ v] , v) ) ;
}
}
for ( int i= 0 ; i< G[ u] . size ( ) ; i++ ) {
int v= G[ u] [ i] . first, w= G[ u] [ i] . second;
if ( dis[ v] > dis[ u] + w) dis[ v] = dis[ u] + w;
}
}
for ( int i= 0 ; i< node[ u] . size ( ) ; i++ ) {
int v= node[ u] [ i] ;
for ( int j= 0 ; j< G[ v] . size ( ) ; j++ ) {
if ( -- in[ color[ G[ v] [ j] . first] ] == 0 ) q. push ( color[ G[ v] [ j] . first] ) ;
}
}
}
for ( int i= 1 ; i<= n; i++ ) {
if ( dis[ i] == inf) cout<< "NO PATH " << endl;
else cout<< dis[ i] << endl;
}
return 0 ;
}