Given an undirected graph described by edges
, where each edge [u, v, cnt]
means there is an edge between nodes u
and v
that is subdivided into cnt
new nodes (i.e., the edge becomes a path of cnt + 1
edges and cnt
new nodes in between), and an integer maxMoves
, your task is to determine how many nodes (including both original and subdivided nodes) are reachable from node 0
using at most maxMoves
moves.
Constraints:
maxMoves
in total moves from node 0
.0
.
At first glance, the problem seems to require expanding the entire graph to include all subdivided nodes, then performing a breadth-first or depth-first search to count the number of reachable nodes from node 0
within maxMoves
steps. However, explicitly building the subdivided graph is inefficient, especially if edge subdivision counts are large.
Instead, we need an approach that efficiently simulates traversals over subdivided edges without actually creating all the intermediate nodes. We realize that each edge can be traversed partially (i.e., we may only reach some subdivided nodes along an edge, depending on our remaining moves), and we need to account for all such reachable nodes.
The problem reduces to a shortest-path computation (like Dijkstra's algorithm) on the original graph, treating the edge weights as the number of subdivided nodes plus one (the length of the subdivided edge). Then, for each edge, we can compute how many subdivided nodes are reached from each side, and sum up all reachable nodes.
We use a modified Dijkstra's algorithm to track the maximum number of moves remaining when reaching each node, and efficiently count reachable subdivided nodes on each edge.
cnt
).[u, v, cnt]
, track how many subdivided nodes are reached from u
and from v
(based on remaining moves after reaching those nodes).[u, v]
is min(cnt, used_from_u + used_from_v)
, where used_from_u
and used_from_v
are the moves spent into the edge from each side.This method avoids explicit graph expansion and efficiently traverses only what is necessary.
Input:
edges = [[0,1,4],[1,2,6],[0,2,8]]
, maxMoves = 10
, n = 3
Brute-force approach:
cnt
), making BFS/DFS O(L), where L is the total number of nodes after subdivision.This is much more efficient and scalable for large graphs and high subdivision counts.
The key insight is to simulate traversals over subdivided edges without explicitly generating the subdivided nodes. By using Dijkstra's algorithm and carefully tracking moves left at each node, we can efficiently count all reachable original and subdivided nodes. This approach avoids the combinatorial explosion of brute-force expansion, resulting in an elegant and efficient solution.
import heapq
from collections import defaultdict
class Solution:
def reachableNodes(self, edges, maxMoves, n):
graph = defaultdict(list)
for u, v, cnt in edges:
graph[u].append((v, cnt))
graph[v].append((u, cnt))
heap = [(-maxMoves, 0)] # (neg moves left, node)
seen = dict()
while heap:
neg_moves, node = heapq.heappop(heap)
moves = -neg_moves
if node in seen and seen[node] >= moves:
continue
seen[node] = moves
for nei, cnt in graph[node]:
next_moves = moves - cnt - 1
if next_moves >= 0 and (nei not in seen or seen[nei] < next_moves):
heapq.heappush(heap, (-next_moves, nei))
ans = len(seen) # Original nodes reached
used = dict()
for u, v, cnt in edges:
a = seen.get(u, 0)
b = seen.get(v, 0)
ans += min(cnt, a + b)
return ans
#include <vector>
#include <queue>
#include <unordered_map>
#include <unordered_set>
using namespace std;
class Solution {
public:
int reachableNodes(vector<vector<int>>& edges, int maxMoves, int n) {
vector<vector<pair<int,int>>> graph(n);
for (auto& e : edges) {
graph[e[0]].push_back({e[1], e[2]});
graph[e[1]].push_back({e[0], e[2]});
}
priority_queue<pair<int,int>> pq;
unordered_map<int,int> seen;
pq.push({maxMoves, 0});
while (!pq.empty()) {
auto [moves, node] = pq.top(); pq.pop();
if (seen.count(node) && seen[node] >= moves) continue;
seen[node] = moves;
for (auto& [nei, cnt] : graph[node]) {
int next_moves = moves - cnt - 1;
if (next_moves >= 0 && (!seen.count(nei) || seen[nei] < next_moves)) {
pq.push({next_moves, nei});
}
}
}
int ans = seen.size();
for (auto& e : edges) {
int a = seen.count(e[0]) ? seen[e[0]] : 0;
int b = seen.count(e[1]) ? seen[e[1]] : 0;
ans += min(e[2], a + b);
}
return ans;
}
};
import java.util.*;
class Solution {
public int reachableNodes(int[][] edges, int maxMoves, int n) {
Map<Integer, List<int[]>> graph = new HashMap<>();
for (int[] e : edges) {
graph.computeIfAbsent(e[0], k -> new ArrayList<>()).add(new int[]{e[1], e[2]});
graph.computeIfAbsent(e[1], k -> new ArrayList<>()).add(new int[]{e[0], e[2]});
}
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(b[0], a[0]));
Map<Integer, Integer> seen = new HashMap<>();
pq.offer(new int[]{maxMoves, 0});
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int moves = curr[0], node = curr[1];
if (seen.containsKey(node) && seen.get(node) >= moves) continue;
seen.put(node, moves);
if (!graph.containsKey(node)) continue;
for (int[] nei : graph.get(node)) {
int next_moves = moves - nei[1] - 1;
if (next_moves >= 0 && (!seen.containsKey(nei[0]) || seen.get(nei[0]) < next_moves)) {
pq.offer(new int[]{next_moves, nei[0]});
}
}
}
int ans = seen.size();
for (int[] e : edges) {
int a = seen.getOrDefault(e[0], 0);
int b = seen.getOrDefault(e[1], 0);
ans += Math.min(e[2], a + b);
}
return ans;
}
}
/**
* @param {number[][]} edges
* @param {number} maxMoves
* @param {number} n
* @return {number}
*/
var reachableNodes = function(edges, maxMoves, n) {
const graph = new Map();
for (let [u, v, cnt] of edges) {
if (!graph.has(u)) graph.set(u, []);
if (!graph.has(v)) graph.set(v, []);
graph.get(u).push([v, cnt]);
graph.get(v).push([u, cnt]);
}
const maxHeap = [[maxMoves, 0]];
const seen = new Map();
while (maxHeap.length) {
maxHeap.sort((a, b) => b[0] - a[0]);
let [moves, node] = maxHeap.shift();
if (seen.has(node) && seen.get(node) >= moves) continue;
seen.set(node, moves);
if (!graph.has(node)) continue;
for (let [nei, cnt] of graph.get(node)) {
let next_moves = moves - cnt - 1;
if (next_moves >= 0 && (!seen.has(nei) || seen.get(nei) < next_moves)) {
maxHeap.push([next_moves, nei]);
}
}
}
let ans = seen.size;
for (let [u, v, cnt] of edges) {
let a = seen.get(u) || 0;
let b = seen.get(v) || 0;
ans += Math.min(cnt, a + b);
}
return ans;
};