算不上是一种统一的算法,大体思路如下:
1.两遍dij跑1,n两个点的单源最短路,同时维护pre数组存最短路
2.枚举任意两点u,v,通过pre数组确认不是最短路上相邻两点后,更新dis1[u]+disn[v]+d[u][v]是否是次短路
不加堆优化的dij是N2的,暴力枚举u,v也是O(N2)的,因此这种思路是无论图是什么情况,都可以在O(N^2)解决。
特别的,如果记录下每条边,那么枚举边时间复杂度O(M),整体时间复杂度与一次dij相同(dij时间复杂度总不小于O(M))。
理论上次短路的题都可以采用这个方法处理,但是有的题目细节太多了,这么写会很麻烦,所以不建议以此为最短路模版。
需要注意的地方包括但不限于:
1.若无向图且允许一边多走,则需要在回溯pre数组过程中更新最短路上邻点u,v的d[u][v]+mindis是否是次短路
2.在1的条件下,若图中有多条最短路,则pre需要开成vector,dfs存储所有回溯结点,遍历所有最短路
3.有重边情况不能只用d[u][v]存储最短的那条,还需要存储每条边的信息,不然会在枚举边时少了这条
4.求严格次短路时每次更新都要判断DIS>mindis才将DIS更新
综上,这种方法十分繁琐,需要注意的点很多。
以下给出包含部分注意事项的P2865 [USACO06NOV] Roadblocks G这道题的实现代码。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 5010;
const int INF = 1e9;
int d[maxn][maxn], cls[maxn], n, m, vis[maxn], ans = INF, minst;
struct Node {int u, v, w;};
vector<int> pre[maxn];
vector<Node> Edge;
void dij(int s,vector<int> &dis)
{for(int i=1;i<=n;i++)dis[i] = INF, vis[i] = 0;dis[s] = 0;pre[s].clear();for(int i=1;i<n;i++){int minv = INF, u = 0;for(int j=1;j<=n;j++){if(dis[j]<minv && vis[j]==0){minv = dis[j];u = j; }}vis[u] = 1;for(int v=1;v<=n;v++){if(dis[v]>dis[u]+d[u][v]){dis[v] = dis[u]+d[u][v];pre[v].clear();pre[v].push_back(u);}else if(dis[v]==dis[u]+d[u][v])pre[v].push_back(u);}}
}
void dfs(int u)
{if(u==n) return;for(int i=0;i<pre[u].size();i++){int v = pre[u][i];ans = min(ans, minst+d[u][v]*2);dfs(v);}
}
signed main()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)d[i][j] = INF;for(int i=1;i<=m;i++){int u, v, w;cin>>u>>v>>w;d[u][v] = d[v][u] = min(d[u][v], w);Edge.push_back({u,v,w});}vector<int> dis1,dis2;dis1.resize(n+1);dis2.resize(n+1);dij(1,dis1);dij(n,dis2);minst = dis1[n];dfs(1);for(int i=0;i<Edge.size();i++){int u = Edge[i].u, v = Edge[i].v, w = Edge[i].w;if((cls[u]==v || cls[v]==u) && w==d[u][v])continue;if(dis1[u]+w+dis2[v]!=minst)ans = min(ans, dis1[u]+w+dis2[v]);if(dis2[u]+w+dis1[v]!=minst)ans = min(ans, dis2[u]+w+dis1[v]); }cout<<ans<<endl;return 0;
}
```cpp尽管这种方式弊端很多,由于枚举了所有可能成为次短路的路径,所以在一些题目中会有妙用。
例如[P1186 玛丽卡](https://www.luogu.com.cn/problem/P1186),需要将最短路上的所有点视作一个序列,并枚举所有边,对序列进行区间修改,并最后通过区间信息得出答案。