Core Logic and Mathematical Principles
In graph theory, the second shortest path is divided into non-strict second shortest path and strict second shortest path. Let the length of the shortest path from the source $s$ to the sink $t$ be $D_{ ext{1st}}$, and the length of the second shortest path be $D_{ ext{2nd}}$:
- Non-strict second shortest path: Allows $D_{ ext{2nd}} = D_{ ext{1st}}$ (in this case, the second shortest path is usually another path with the same length as the shortest path, but with a different edge sequence).
- Strict second shortest path: Must satisfy $D_{ ext{2nd}} > D_{ ext{1st}}$.
1. Splitting the State Topology Space
The traditional Dijkstra algorithm maintains a one-dimensional distance vector $dist[u]$ on the vertex set $V$. To solve the second shortest path, the state space must be expanded horizontally to two dimensions:
- $dist[u][0]$: The shortest path length from the source $s$ to node $u$.
- $dist[u][1]$: The second shortest path length from the source $s$ to node $u$.
2. Heap Optimization Cascade Relaxation Principle
The essence of the second shortest path is derived from the "residual edges of the shortest path" or the "outgoing edges of the second shortest path". The Dijkstra topology expansion based on a priority queue (min-heap) also applies to the second shortest path, with its core mathematical foundation being the secondary cascading update of states. Any edge $(u, v)$ with weight $w$ may trigger four progressive mathematical state transformations when dequeued. Through these four branches, the algorithm can transfer any excess weight fluctuations to $dist[v][1]$, while the overall time complexity remains stable at the standard $O((V+E) ext{log} V)$.
Algorithm Derivation and State Design
Let the current node dequeued from the priority queue be $u$, and its topological path weight be $d$ (at this time $d$ may be the shortest path or the second shortest path). Traverse its outgoing edges $(u, v)$, with edge weight $w$. Define the new candidate path length as $cost = d + w$.
Cascade State Transition Equations
-
Branch A: Breakthrough Current Shortest Path
If $cost < dist[v][0]$, it indicates that a shorter shortest path has been found. In this case, the original Shortest degenerates into Second Shortest, and the new path takes over Shortest.$$ ext{State Transition}: dist[v][1] = dist[v][0], \\ dist[v][0] = cost$$
Note: Both of these new states must be pushed into the priority queue.
-
Branch B: Approaching Current Second Shortest Path (for strict second shortest path determination)
If $cost > dist[v][0]$ and $cost < dist[v][1]$, it indicates that $cost$ is sandwiched between the shortest path and the second shortest path, and the second shortest path is updated directly.$$ ext{State Transition}: dist[v][1] = cost$$
-
Branch C: Special Case for Non-Strict Second Shortest Path
If the problem requires non-strict second shortest path, and $cost == dist[v][0]$, although it cannot update the shortest path, it belongs to another path parallel to it and is qualified to update the second shortest path, triggering: $dist[v][1] = cost$. If strict second shortest path is required, this branch must be abandoned.
C++ Standard Source Code (Strict Second Shortest Path Standard Heap Optimization Template)
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
const long long INF = 0x3f3f3f3f3f3f3f3fLL;
const int MAXN = 100005;
struct Edge {
int to;
long long weight;
};
struct HeapNode {
long long d;
int u;
int type; // 0 indicates current value is the shortest path estimate, 1 indicates second shortest path estimate
bool operator>(const HeapNode& rhs) const {
return d > rhs.d;
}
};
std::vector<Edge> adj[MAXN];
long long dist[MAXN][2]; // dist[u][0]: shortest path, dist[u][1]: second shortest path
bool vis[MAXN][2]; // Two-dimensional marking array, intercepting redundant dequeuing of shortest and second shortest paths
void dijkstra_2nd(int s, int n) {
for (int i = 1; i <= n; ++i) {
dist[i][0] = INF;
dist[i][1] = INF;
vis[i][0] = false;
vis[i][1] = false;
}
std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode> > pq;
dist[s][0] = 0;
pq.push(HeapNode{0, s, 0});
while (!pq.empty()) {
HeapNode curr = pq.top();
pq.pop();
int u = curr.u;
int type = curr.type;
// Fatal pitfall: Must perform two-dimensional vis check, otherwise stale data in the heap will cause complexity to skyrocket
if (vis[u][type]) continue;
vis[u][type] = true;
for (size_t i = 0; i < adj[u].size(); ++i) {
int v = adj[u][i].to;
long long w = adj[u][i].weight;
long long cost = curr.d + w; // Path accumulation based on current dequeued state
// Branch A: Refresh shortest path, old shortest path becomes second shortest path
if (cost < dist[v][0]) {
dist[v][1] = dist[v][0];
pq.push(HeapNode{dist[v][1], v, 1}); // Push old value downgrade into heap
dist[v][0] = cost;
pq.push(HeapNode{dist[v][0], v, 0}); // Push new shortest path into heap
}
// Branch B: Strictly sandwiched between shortest and second shortest paths, only update second shortest path
else if (cost > dist[v][0] && cost < dist[v][1]) {
dist[v][1] = cost;
pq.push(HeapNode{dist[v][1], v, 1});
}
// If the problem requires **non-strict second shortest path**, then cancel the cost > dist[v][0] restriction above, change it to cost >= dist[v][0]
}
}
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int n, m;
if (!(std::cin >> n >> m)) return 0;
for (int i = 0; i < m; ++i) {
int u, v;
long long w;
std::cin >> u >> v >> w;
adj[u].push_back(Edge{v, w});
adj[v].push_back(Edge{u, w}); // Build edges bidirectionally for undirected graph
}
dijkstra_2nd(1, n);
if (dist[n][1] == INF) {
std::cout << -1 << "\n";
} else {
std::cout << dist[n][1] << "\n"; // Output strict second shortest path to sink n
}
return 0;
}
NOIP Practical Pitfall Guide
- The
vismarking array is not allocated in two-dimensional state space:
Many contestants directly use the traditional one-dimensional Dijkstra'svis[u]. Once the shortest path of nodeuis determined andvis[u]=true, subsequent legitimate second shortest path dequeue states of that node will be brutally intercepted byif(vis[u]) continue;. This leads to the inability of the second shortest path state to continue relaxing forward, and the second shortest path obtained usually remains at the initial valueINF, resulting in a direct zero in the evaluation. - Missing the "downgrade propagation" of the old value when updating the shortest path:
When triggering Branch A, i.e.,cost < dist[v][0], it is necessary to first executedist[v][1] = dist[v][0]and push{dist[v][1], v, 1}into the heap, and then updatedist[v][0]. Ifdist[v][0] = costis directly overwritten, the originally optimal solution that could become the second shortest path will be ruthlessly erased, destroying the continuity of state transitions.
Classic NOIP/Luogu Real Questions
1. Luogu P2865 [USACO06NOV] Roadblocks G
- Problem Description:
Given an undirected graph with $N$ vertices and $M$ bidirectional edges, find the length of the strict second shortest path from node $1$ to node $N$. The data guarantees that there is at least one second shortest path. - Core Idea and Essence of the Problem:
A benchmark problem for standard strict second shortest path. Since it is an undirected graph and requires strictly greater than the shortest path, it perfectly fits the two-dimensional cascading Dijkstra algorithm. The core idea is to control the state transition using the above source code, strictly limitingcost > dist[v][0]in the relaxation conditions. After the heap structure is processed,dist[N][1]stores the physical strict second shortest path.
2. Luogu P1491 Set Position
- Problem Description:
Given an undirected weighted graph on a two-dimensional plane, where each point has coordinates and the edge weights are the Euclidean distances between the two points. Find a second shortest path from the starting point to the endpoint, requiring that the second shortest path cannot consist of exactly the same edges as the shortest path (i.e., non-strict second shortest path, but the path itself as a set of edges cannot completely overlap with the shortest path). - Core Idea and Essence of the Problem:
Using the edge deletion method to find the second shortest path.
Due to the usually small data range of this problem ($N \le 200$), and the requirement for the path shape level of "second shortest", one can first run a standard Dijkstra algorithm to find the absolute shortest path and record all edges traversed by the shortest path (at most $N-1$ edges). Then, one can iteratively delete a certain edge on the shortest path in the adjacency list (by marking it as impassable), and run a complete standard single-source shortest path again for each edge deletion. Among all the shortest path results after edge deletions, the minimum value obtained is essentially the second shortest path that differs from the original shortest path by at least one edge. This clever method utilizes the discrete characteristics of topological paths, with a time complexity of $O(N \cdot (V+E)\text{log} V)$, and a very low constant when the data volume is moderate, making it highly tolerant for problem-solving.