Core Logic and Mathematical Principles
The essence of the Single Source Shortest Path (SSSP) problem is to find the minimum edge weight path from a specific source point $s$ to all other nodes $v \in V$ in a weighted graph $G=(V, E)$. Under the NOIP system, the Dijkstra heap optimization algorithm and the SPFA algorithm represent two distinctly different topological approximation paradigms.
1. Greedy Paradigm: Dijkstra's Algorithm
The core of Dijkstra's algorithm is a greedy expansion based on the topological blue and white point sets.
- Mathematical Principle: Nodes are divided into two sets: the set of confirmed shortest paths (blue points) $S$ and the set of unconfirmed floating points (white points) $T$. Each time, one node $u$ that is currently closest to the source point $s$ is forcibly selected from $T$ and added to $S$.
- Non-negative Weight Dependency: The mathematical foundation upon which this algorithm relies is monotonically non-decreasing. If there are negative weight edges in the graph, the shortest path of nodes already included in $S$ may be refreshed by subsequent negative weight cycles or edges, leading to the collapse of the greedy strategy.
- Complexity Theorem: By maintaining the minimum value of the $T$ set with a priority queue (binary heap), the complexity of a single relaxation operation is $O(\log V)$, and the total time complexity is strictly controlled at $O((V+E)\log V)$.
2. Dynamic Approximation Paradigm: SPFA Algorithm
SPFA (Shortest Path Faster Algorithm) is essentially the Bellman-Ford algorithm optimized with a queue, belonging to the iterative approximation of dynamic programming.
- Mathematical Principle: The graph undergoes multiple rounds of relaxation. Only those nodes whose shortest path estimates were successfully updated in the previous step can trigger the relaxation of subsequent nodes through their outgoing edges. Therefore, a queue is used to store nodes that can relax others due to their own values decreasing.
- Death Boundary (Algorithm Degradation): The theoretical average complexity of SPFA is $O(kE)$ (where $k$ is a constant, generally $k \le 2$). However, its worst-case time complexity is a standard $O(VE)$. In any graph without negative weight edges (especially grid graphs, specially constructed chain graphs, or dense graphs), the problem setters can construct special malicious data to "trap SPFA" (such as forward star with backward chains or special long straight chains), causing nodes in the queue to repeatedly enter, resulting in topological oscillation and the algorithm degenerating into a brute-force search of Bellman-Ford.
Algorithm Derivation and State Design
Define $dist[u]$ as the current shortest path estimate from the source point $s$ to node $u$.
1. Triangle Inequality and Relaxation Operation
For any edge $(u, v) \in E$ with weight $w$, the relaxation operation is the core state transition equation of the entire shortest path algorithm:
$$dist[v] = \min(dist[v], dist[u] + w)$$
Physical Meaning: It checks whether the path from $s$ to $v$ via $u$ is shorter than the currently known direct path or the path to $v$ through other means.
2. Dijkstra Heap Optimization State Design
The state stored in the priority queue is a pair $(d, u)$, where $u$ is the node index and $d$ is the current $dist[u]$. The top element of the binary heap satisfies:
$$u_{\text{next}} = \arg\min_{v \in T} (dist[v])$$
Since the default of C++ std::priority_queue is a max heap, the comparison operator of the structure must be overloaded to transform it into a min heap during implementation.
C++ Standard Source Code (Dijkstra Heap Optimization Standard Industrial Template)
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
const long long INF = 0x3f3f3f3f3f3f3f3fLL; // Use a sufficiently large long integer to prevent overflow
const int MAXN = 100005;
struct Edge {
int to;
long long weight;
};
struct HeapNode {
long long d;
int u;
// Critical low-level error prevention: must strictly implement min-heap comparison, otherwise max-heap will cause complexity degradation to brute-force
bool operator>(const HeapNode& rhs) const {
return d > rhs.d;
}
};
std::vector<Edge> adj[MAXN];
long long dist[MAXN];
bool vis[MAXN]; // Mark whether it has been added to the set S
void dijkstra(int s, int n) {
// State initialization
for (int i = 1; i <= n; ++i) {
dist[i] = INF;
vis[i] = false;
}
// Declare min-heap
std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode> > pq;
dist[s] = 0;
pq.push(HeapNode{0, s});
while (!pq.empty()) {
HeapNode current = pq.top();
pq.pop();
int u = current.u;
// Key pitfall: there may be old, longer distance redundant nodes in the heap, must skip them
if (vis[u]) continue;
vis[u] = true; // Officially included in the set S
for (size_t i = 0; i < adj[u].size(); ++i) {
int v = adj[u][i].to;
long long w = adj[u][i].weight;
// Triangle inequality state transition
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
pq.push(HeapNode{dist[v], v});
}
}
}
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int n, m, s;
if (!(std::cin >> n >> m >> s)) 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}); // If it's an undirected graph, need to push_back in both directions
}
dijkstra(s, n);
for (int i = 1; i <= n; ++i) {
if (dist[i] == INF) {
std::cout << 2147483647 << " "; // Corresponds to the unreachable output value required by some problems
} else {
std::cout << dist[i] << " ";
}
}
std::cout << "\n";
return 0;
}
NOIP Practical Avoidance Guide
- Blindly trusting SPFA leading to being trapped in competitions: Some contestants, due to the habit of the concise SPFA code or the desire for its ability to handle negative weights, blindly choose SPFA for routine problems without any negative weight edges. In recent NOIP/provincial evaluations, for any non-negative single-source shortest path problem, the problem setter will definitely construct special malicious data to trap SPFA. Once SPFA is chosen, it is equivalent to betting $O((V+E)\log V$) against the worst-case scenario of $O(VE)$, directly leading to TLE and losing over 60% of the score. When there are no negative weight edges, only Dijkstra heap optimization can be used.
- Missing
if (vis[u]) continue;in heap optimization leading to time explosion: Linear relaxation operations can cause the same node $u$ to be updated multiple times and repeatedly pushed into the priority queue. If the lazy deletion checkif (vis[u]) continue;is not done, when facing dense graphs, the dequeued nodes will carry a lot of outdated dirty data and re-execute theforloop to traverse the outgoing edges, which can cause the actual size of the priority queue to balloon to $O(E)$, leading to a direct degradation of the algorithm's time complexity, failing to achieve the expected logarithmic optimization effect.
Classic NOIP/Luogu Problems
1. Luogu P4779 [Template] Single Source Shortest Path (Standard Version)
- Problem Description: Given a weighted directed graph with $N$ points and $M$ directed edges, find the shortest distance from the source point $S$ to all points. The data satisfies $N \le 10^5, M \le 2 \times 10^5$, and edge weights are non-negative.
- Core Idea and Essence of the Problem:
The purest single-source shortest path problem in a non-negative weighted directed graph. The data range clearly eliminates the possibility of brute-force $O(V^2)$ Dijkstra and SPFA. The core idea is to use the standard
std::priority_queueto maintain a min-heap. Each successful relaxation operation will push the state into the queue. During dequeuing, thevisarray is used to intercept, ensuring that each edge is relaxed only once and each node is expanded as a benchmark point only once, making it a benchmark code that must be written without errors.
2. Luogu P3003 [USACO10MAR] Grass Traveling G
- Problem Description: Given an undirected non-negative weighted graph composed of $N$ points and $M$ bidirectional edges. Given two specific destinations $A$ and $B$, and a starting point $P$. Design a route such that the total travel distance from $P$ to $A$ and then to $B$, or from $B$ to $A$ is minimized.
- Core Idea and Essence of the Problem: A linear combination of multi-source single-source shortest paths. The distances in an undirected graph are symmetric, i.e., $\text{dist}(A, B) = \text{dist}(B, A)$. The total distance from $P$ to $A$ and then to $B$ is $\text{dist}(P, A) + \text{dist}(A, B)$; the total distance from $B$ to $A$ is $\text{dist}(P, B) + \text{dist}(B, A)$. The physical essence of the problem is not to run complex dynamic programming but to run the standard heap-optimized Dijkstra algorithm once each from $P$, $A$, and $B$, obtaining three single-source shortest path distance vectors. The final result can be directly calculated using the expression $\min(\text{dist}_P[A], \text{dist}_P[B]) + \text{dist}_A[B]$. The time complexity is a constant-level increase of $O(3 \times (V+E)\log V)$, still extremely efficient.