THE WHY BEHIND EVERY DSA PROBLEM
SHEET: STRIVER A2Z · REV 0.1
UNDERSTOOD: 0 / 79 DRAFTED
STEP 15 · GRAPHS · COUNT ALONGSIDE THE DISTANCE · HARD

Number of Ways to Arrive at Destination

WHAT IT SAYS

Count the distinct shortest-time routes from intersection 0 to intersection n−1 in a weighted road network, modulo 1e9+7.

WHAT IT'S REALLY ASKING

"Shortest routes compose: every shortest route into v arrives through some neighbour u where dist[u] + edge = dist[v], and it extends one of u's shortest routes. So a counter can ride shotgun on Dijkstra — strictly better arrival: inherit the count; exactly equal arrival: ADD the counts. Enumerate nothing; the ways aggregate as the distances settle."

THE INSIGHT LADDER — FROM BRUTE FORCE TO OPTIMAL

Find the shortest time, then enumerate matching routes

Exponential — route enumeration ignores everything the distances proved

Run Dijkstra for the optimal time, then DFS all routes from source to destination, counting those whose total equals the optimum.

WHERE THE WORK IS WASTED — Counting by listing is the cardinal sin when counts compose: the number of shortest routes to v is fully determined by the numbers at its shortest-path predecessors — a sum, computable in O(degree) — yet enumeration materialises every route individually, re-walking shared prefixes once per route that uses them. A graph can pack exponentially many shortest routes into linearly many edges (diamond chains double the count per stage); enumeration pays per route, arithmetic pays per edge. Word Ladder II had to enumerate because it wanted the paths themselves; this problem asks only HOW MANY, and the downgrade from 'list' to 'count' is precisely what makes the exponential collapse available.

!
KEY OBSERVATION — THE UNLOCKlink

Ways compose over the shortest-path DAG — sum at equality.

The structural fact underneath: keep exactly the edges (u, v) satisfying dist[u] + w(u,v) = dist[v] — the 'tight' edges — and they form a DAG (dist strictly increases along each, so no cycles), whose source-to-v paths are IN BIJECTION with the shortest routes to v. Every shortest route uses only tight edges (a slack edge anywhere would overshoot the total), and every tight-edge path from the source totals exactly dist[v]. So ways[v] obeys clean DAG arithmetic: ways[source] = 1 (the empty route), and ways[v] = sum of ways[u] over tight in-edges — counts flowing forward through the DAG like Pascal's rule, because route sets through distinct predecessors are disjoint (different last edge) and exhaustive. Now the fusion that removes even the second pass: Dijkstra settles nodes in non-decreasing dist order, which is a valid topological order of the tight DAG — every tight predecessor of v settles before v (strictly smaller dist... with the equal-dist edge-weight-zero caveat absent here since weights are positive). So the sum can be accumulated DURING relaxation, and the two relaxation outcomes map exactly onto counting rules. Strict improvement (dist[u] + w < dist[v]): all previously counted routes to v just became non-shortest — obsolete — so OVERWRITE: ways[v] = ways[u]. Exact tie (dist[u] + w == dist[v]): a new family of shortest routes arriving via u, disjoint from those already counted (different final edge) — ADD: ways[v] += ways[u]. The overwrite-versus-add pair is the entire algorithm; everything else is Network Delay Time's engine unchanged. Two disciplines keep it honest. Modulo 1e9+7 on every addition — the counts are the exponential objects here even though the computation is polynomial, and they overflow fast. And only trust accumulations at SETTLED sources: with lazy-deletion Dijkstra, process a node's outgoing relaxations when it is popped fresh (first pop = settled = ways[u] final); adding from a node whose own count later grows would undercount downstream. The invariant to keep in your head: ways[v] is provisional until v settles, final after — mirroring dist[] exactly, which is why they can share one pass.

Dijkstra with a parallel ways[] and the overwrite/add rule

O(E log V) time — Dijkstra's own bill — O(V) extra for the counter array

dist[] = infinity, ways[] = 0, dist[0] = 0, ways[0] = 1; heap (0, 0). Pop fresh (skip stale); for each edge (u, v, w): nd = dist[u] + w; if nd < dist[v]: dist[v] = nd, ways[v] = ways[u], push; else if nd == dist[v]: ways[v] = (ways[v] + ways[u]) mod M. Use 64-bit times (weights up to 1e9 across 200 nodes). Return ways[n−1].

WHAT YOU TRADED — The fused pass is optimal but couples two invariants (distance finality and count finality) into one loop — the classic bug is adding counts from unsettled or stale sources, invisible on small tests; the decoupled alternative (Dijkstra first, then a separate topological accumulation over tight edges) costs a second pass and buys separability of concerns. The transferable lesson: 'how many optimal X' rides on any optimality computation via overwrite-on-improve, add-on-tie — the value function defines a DAG, ties are where branches merge, and counting is Pascal's rule flowing through it. Downgrade further to 'does one exist' and even the counter disappears.
WATCH THE IDEA RUN
2222190d0 ×11234
better ⇒ overwrite · equal ⇒ SUM the two rules
ways to node 4
Enumerating every shortest path and counting them is exponential — the paths can branch and re-merge. But we never need the paths. We need their NUMBER, and a number can be carried alongside the distance.
step 1 / 13
THE PATTERN — SO YOU RECOGNIZE IT NEXT TIME

Overwrite on Improve, Add on Tie

YOU'LL SEE IT AGAIN WHEN

  • COUNT of optimal solutions is asked — not the solutions themselves — so per-node counters can replace enumeration entirely.
  • Tight edges (where the value recurrence holds with equality) form a DAG, and counts compose disjointly across a node's tight predecessors.
  • The optimiser's settle order is a topological order of that DAG — the counter can ride the same pass if sources are only trusted once settled.

SAME BLUEPRINT, DIFFERENT PROBLEM

Network Delay Time (the bare engine this decorates)Word Ladder II (when the paths themselves are demanded — enumeration returns)Unique Paths (the same composition on a grid DAG)Count of Shortest Paths in Unweighted Graph (BFS variant, same two rules)
The bar isn't "solved it once." It's "could rebuild it from the observation."