三种最短路算法
Floyd
可求出任意两点间最短路,适合小规模稠密图(点数不超过 500 500 500)。
复杂度 O ( n 3 ) O(n^3) O(n3)
注意:如果 n n n超过 800 800 800,且图比较稀疏,可使用 n n n次dijkstra代替Floyd。
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][j],dis[i][k]+dis[k][j]);
堆优化dijkstra
可求出单源最短路,适合大规模稀疏图。
复杂度 O ( m log n ) O(m\log n) O(mlogn)
不能处理带负权的图!!!!!!
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> > pq;
void dijkstra(int S){
memset(dis,63,sizeof(dis));
memset(vis,0,sizeof(vis));
pq.push(pii(dis[S]=0,S));
while(!pq.empty()){
int u=pq.top().second;
pq.pop();
if(vis[u])continue;
vis[u]=1;
for(int i=fir[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]>dis[u]+w[i])
pq.push(pii(dis[v]=dis[u]+w[i],v));
}
}
}
SPFA
可求出单源最短路,且可以处理负权。适合带负权的大规模稀疏图。
复杂度:下界 O ( m ) O(m) O(m),上界 O ( n m ) O(nm) O(nm)。
(关于如何卡到上界,见NOI2018 D1T1)
关于SPFA,它死了
在边权均为 01 01 01的图中复杂度是 O ( m ) O(m) O(m)。
queue<int> q;
void SPFA(int S){
memset(dis,63,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[S]=0,vis[S]=1;
q.push(S);
while(!q.empty()){
int u=q.front();
q.pop(),vis[u]=0;
for(int i=fir[u];i;i=nxt[i]){
int v=to[i];
if(dis[v]>dis[u]+w[i]){
dis[v]=dis[u]+w[i];
if(!vis[v])vis[v]=1,q.push(v);
}
}
}
}
Part 2.4.1:最短路
例题2.4.1:[FAIOJ2344]寻宝之路
Floyd求出任意两点最短路后,再对 d i s [ A i ] [ A i + 1 ] dis[A_i][A_{i+1}] dis[Ai][Ai+1]求和。
复杂度 O ( n 3 ) O(n^3) O(n3)
例题2.4.2:[FAIOJ30075]农场派对
分别在原图和反图上求出 x x x点到其他点的最短路,再统计最大值。
复杂度 O ( m log n ) O(m\log n) O(mlogn)
例题2.4.3:[FAIOJ30078]新年好
对于 1 , a , b , c , d , e 1,a,b,c,d,e 1,a,b,c,d,e五个点分别求出到其他点单源最短路,然后全排列枚举所有 5 ! 5! 5!种方案统计最小值。
复杂度 O ( m log n + 5 ! ) O(m\log n+5!) O(mlogn+5!)
例题2.4.4:[FAIOJ30074]架设电话线
最小化第 k + 1 k+1 k+1大
类似于最小化最大值问题,二分答案 x x x。
将边权 ≤ x \leq x ≤x的边全部视为 0 0 0,边权 > x >x >x的边全部视为 1 1 1。
然后求最短路,如果 1 1 1到 n n n的最短路 ≤ k \leq k ≤k就说明 x x x可行,应下调;否则上调。
复杂度 O ( m log n log W ) O(m\log n\log W) O(mlognlogW)
例题2.4.5:[ZJOI2006][FAIOJ20601]物流运输
本题看似很复杂,但数据范围极小,所以可以考虑暴力一些的做法,比如:求出每一段时间的最短路。
设 f [ i ] = f[i]= f[i]=前 i i i天的最优解,则有转移
f [ i ] = min { f [ j − 1 ] + ( i − j + 1 ) ∗ L ( j , i ) + K } , 1 ≤ j ≤ i f[i]=\min\{f[j-1]+(i-j+1)*L(j,i)+K\},1\leq j\leq i f[i]=min{ f[j−1]+(i−j+1)∗L(j,i)+K},1≤j≤i
其中 L ( j , i ) L(j,i) L(j,i)表示从第 j j j天到第 i i i天,不改变航线,从 1 1 1到 n n n的最短路。用dijkstra
等算法求解均可。
复杂度 O ( n 2 E log V ) O(n^2E\log V) O(n2ElogV)
Part 2.4.2:分层图最短路
分层图也是图论中一种常见的“拆点”方法。我们通过一些例子具体理解。
例题2.4.6:[FAIOJ1650]飞行路线
显然,如果 k = 0 k=0 k=0,那么本题就是一道经典的最短路模型。
但本题允许我们将 k k k条边长视为 0 0 0。
首先指出一种比较显然的贪心是错误的:把最短路中最长的 k k k条边视为 0 0 0。
很简单的例子:
3 3 1
0 2
0 1 1
1 2 1
0 2 3
很明显答案是0
。而上面的贪心会得到1
。
考虑到其实每个点都有 k + 1 k+1 k+1种状态:用了 0 , 1 , … , k 0,1,\dots,k 0,1,…,k次免费票。
因此将每个点 u u u拆成 k + 1 k+1 k+1个点 u 0 , u 1 , … , u k u_0,u_1,\dots,u_k u0,u1,…,uk
然后对于每条原图中的边 ( u , v , w ) (u,v,w) (u,v,w),对于每个 k k k,连边 ( u k , v k , w ) (u_k,v_k,w) (uk,vk,w),表示不用免费票;连边 ( u k , v k + 1 , 0 ) (u_k,v_{k+1},0) (uk,vk+1,0),表示用免费票。
然后做以 S 0 S_0 S0为起点的单源最短路即可。
复杂度 O ( k m log n ) O(km\log n) O(kmlogn)。
例题2.4.7:[NOIP2009][FAIOJ10092]最优贸易
本题有多种方法:贪心,搜索等。但本题也可以用分层图最短路做。
每个点都可以看做有三种状态:没交易过;已经买完但没卖;已经卖完。
因此建立分层图:
原图中的边权均为 0 0 0;
第一层的每个点向第二层相同的点连边,边权为卖出价格;
第二层的每个点向第三层相同的点连边,边权为买入价格的相反数。
然后求出第一层的 1 1 1号点到第三层的 n n n号点的最短路即可。
复杂度 O ( m log n ) O(m\log n) O(mlogn)。
例题2.4.8:[FAIOJ330]汽车加***驶问题
本题条件很多,但实际上只需分类讨论出向每个方向行驶所需费用即可。
首先,车在每个点的状态可以看做有 k + 1 k+1 k+1种:剩 0 , 1 , 2 , … , k 0,1,2,\dots,k 0,1,2,…,k格油。因此建立 k + 1 k+1 k+1层分层图。每层图都是一张网格图。
对于同一层之间的点,因为汽车在任何情况下行驶都会耗油,所以同一层之间的点不连边;
第 i i i层与自己相邻的 4 4 4个点都向第 i i i层的点连边表示耗一格油。
第 i i i层与自己相邻的 4 4 4个点都向第 k k k层的点连边表示加满油后再走一格(因为加油是强制的)。
对于非加油站的点,向上一层
向上和向左连边边权为 0 0 0(没有费用)
向下和向右连边边权为 B B B(过路费)
向第 k k k层
向上和向左连边边权为 A + C A+C A+C(增设加油站,同时加满油)
向下和向右连边边权为 A + B + C A+B+C A+B+C(过路费+油费)
对于加油站的点,向第 k k k层
向上和向左连边边权为 C C C
向下和向右连边边权为 B + C B+C B+C
然后求 ( 1 , 1 , k ) (1,1,k) (1,1,k)到 ( n , n , i ) , 0 ≤ i ≤ k (n,n,i),0\leq i\leq k (n,n,i),0≤i≤k的最短路即可。
复杂度为 O ( k n 2 log n ) O(kn^2\log n) O(kn2logn)
Part 2.4.3:最短路图
定义:所有可能在最短路,即满足 d i s [ u ] + w ( u , v ) = d i s [ v ] dis[u]+w(u,v)=dis[v] dis[u]+w(u,v)=dis[v]的边构成的图。
最短路图是一张DAG(有向无环图)。
因为DAG上的状态满足DP“无后效性”的要求,所以在DAG上是可以DP的。DP过程和树形DP类似,DP的顺序是图的拓扑序。
queue<int> q;
for(int i=1;i<=n;++i)if(!d[i])q.push(i);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=fir[u];i;i=nxt[i]){
int v=to[i];
if(!--d[v])q.push(v);
//DP转移
}
}
例题2.4.9:[FAIOJ30077]最短路计数
本题是最短路图上DP的模板题。
建立最短路图后,对于每条边,如果有 d i s [ u ] + W ( u , v ) = d i s [ v ] dis[u]+W(u,v)=dis[v] dis[u]+W(u,v)=dis[v],则进行转移 f [ v ] + = f [ u ] f[v]+=f[u] f[v]+=f[u]。否则不转移。
例题2.4.10:[SDOI2009][luogu2149]Elaxia的路线
先预处理出 S 1 , T 1 , S 2 , T 2 S_1,T_1,S_2,T_2 S1,T1,S2,T2到其他顶点的最短路。
对于一个点 x x x,如果有 d i s ( S 1 , x ) + d i s ( x , T 1 ) = d i s ( S 1 , T 1 ) , d i s ( S 2 , x ) + d i s ( x , T 2 ) = d i s ( S 2 , T 2 ) dis(S_1,x)+dis(x,T_1)=dis(S_1,T_1),dis(S_2,x)+dis(x,T_2)=dis(S_2,T_2) dis(S1,x)+dis(x,T1)=dis(S1,T1),dis(S2,x)+dis(x,T2)=dis(S2,T2),则说明 x x x在公共最短路上。
如果 x , y x,y x,y都在公共最短路上,那么 x , y x,y x,y在任一条最短路的路径也就都在公共最短路上。
因此暴力枚举 x , y x,y x,y并进行上述判断,然后对 ∣ d i s ( S 1 , x ) − d i s ( S 1 , y ) ∣ |dis(S_1,x)-dis(S_1,y)| ∣dis(S1,x)−dis(S1,y)∣取最大值即可。
复杂度 O ( n 2 + m log n ) O(n^2+m\log n) O(n2+mlogn)。
此算法可通过本题,然而它看起来是错的。。。
最短路图是一张DAG而非只有一条链,所以 x , y x,y x,y可能会在不同的链上,然后上面的 d i s dis dis之差就错了。
但由于这个错误的 d i s dis dis之差不会影响答案(最长公共路径一定会枚举到,而且错误的答案不会大于正确的答案),所以也可以通过。
但是这个 n 2 n^2 n2很麻烦(这道题其他的部分都是 O ( m log n ) O(m\log n) O(mlogn),其实是可以加强出到 n , m ≤ 1 0 5 n,m\leq 10^5 n,m≤105的)。
所以正解仍然是DAG上的DP。设 f [ u ] = f[u]= f[u]=终点为 u u u的最长公共路径。
在第一张DAG图上DP,如果 ( u , v ) (u,v) (u,v)在第二张DAG图上则更新 f [ v ] f[v] f[v]的值。
最后取所有 f [ ] f[] f[]的最大值即可。
复杂度 O ( m log n ) O(m\log n) O(mlogn)。