Jungle Supply Chain Optimization — Problem Statement & Solution Guide
Problem Description
Jungle Supply Chain Optimization
You are given a rooted tree with n nodes (node 1 is the root). Each node i represents a supply hub and has an integer capacity c[i] (0 ≤ c[i] ≤ 10⁹). Cargo originates at the root and can be split arbitrarily among the children of any hub. The total amount of cargo that passes through a hub (including cargo that is split further down) must not exceed its capacity. Determine the maximum total amount of cargo that can be sent from the root to the leaves without violating any hub’s capacity.
Input
- The first line contains an integer n — the number of hubs.
- The second line contains n space‑separated integers c[1] … c[n] — the capacities.
- Each of the next n‑1 lines contains two integers u and v describing an undirected edge; the tree is rooted at 1.
Output
- A single integer: the maximum total cargo that can be transported from the root to the leaves while respecting all capacities.
Explanation of the solution approach
Process the tree in a post‑order (bottom‑up) traversal. For a leaf i, the maximum cargo that can leave the leaf is c[i]. For an internal node i, let S be the sum of the maximum cargo values of its children. The node can forward at most min(c[i], S) units upward because its own capacity limits the total flow through it. The value computed for the root is the answer. This linear scan over the tree runs in O(n) time and O(n) memory.
DSA Pattern Breakdown
DSA Pattern Breakdown
"Jungle Supply Chain Optimization"
WHY DOES IT MATTER?
The pattern is a classic "tree DP with min‑sum aggregation" which appears whenever local limits constrain global distribution, such as bandwidth caps, memory budgets, or resource quotas in hierarchical systems.
OPTIMIZATION CHALLENGE
The key insight is recognizing that the feasible flow of a subtree is independent of sibling subtrees once their own capacities are known, allowing a simple O(1) combine step (sum then min) at each node, collapsing an exponential search space to linear time.
REAL-WORLD CONNECTION
Think of a corporate budget hierarchy: each department has a spending ceiling, and the total spend of a division cannot exceed its own ceiling nor the sum of its sub‑departments' budgets. The same min‑sum rule determines the feasible allocation.
During an interview, compute child capacities first and store them in a variable; avoid recomputing sums by using a single accumulator while traversing children, and remember to use 64‑bit integers because capacities can reach 1e9 and sums may overflow 32‑bit.
COMPLEXITY AT A GLANCE
O(n)O(n)Core Theory — Why This Approach?
The problem models a flow distribution over a rooted tree where each node imposes a hard upper bound on the total amount of cargo that can traverse it. A naive view might treat each edge independently, but because cargo can be arbitrarily split, the limiting factor at any hub is the aggregate of flows destined for its entire subtree, not just a single downstream path. The optimal paradigm is a bottom‑up dynamic programming on the tree: for each leaf the feasible flow equals its capacity, and for an internal node the feasible flow is the minimum of its own capacity and the sum of feasible flows of its children. This recurrence captures the intuition that a hub can forward at most what it can receive (its capacity) and cannot exceed the total demand its children can absorb. The algorithm runs in linear time by a single depth‑first search, avoiding the exponential blow‑up of trying all split configurations.
Interview Questions on This Problem
Q1How would you compute the maximum cargo that can be sent from the root respecting all hub capacities?
Perform a post‑order DFS; for each leaf return its capacity, and for each internal node return min(capacity[node], sum of returned values from its children). The answer is the value returned at the root.
Q2Why does a greedy top‑down distribution fail for this problem?
A top‑down greedy approach decides how much to send to each child before knowing the children’s downstream limits, which can cause over‑allocation to a subtree that cannot forward the excess, violating capacity constraints. The correct solution must first know each subtree’s maximum absorbable flow, which is naturally obtained bottom‑up.
Q3Can this problem be reduced to a classic network flow model? If so, how?
Yes. Create a source connected to the root with infinite capacity, add edges from each node to its children with infinite capacity, and set a node‑capacity edge from each node to a sink equal to c[i]. The maximum flow from source to sink equals the answer, but the specialized tree DP solves it in O(n) without building a flow network.
Examples
Input
3 10 5 7 1 2 1 3
Output
10
Explanation: Node 2 and node 3 are leaves, their limits are 5 and 7 respectively, so together they can receive 5+7=12 units. The root (node 1) can only forward up to its capacity 10, therefore the maximum total cargo is min(10,12)=10.
Input
4 8 6 4 5 1 2 2 3 2 4
Output
6
Explanation: Leaves are nodes 3 (capacity 4) and 4 (capacity 5); together they can accept 9 units. Node 2 can forward at most min(6,9)=6 units to its parent. The root (node 1) has capacity 8, but it can only receive what node 2 can send, so the answer is min(8,6)=6.
Input
5 15 10 12 3 9 1 2 1 3 3 4 3 5
Output
15
Explanation: Leaves: node 2 (10), node 4 (3), node 5 (9). Node 3 can forward at most min(12,3+9)=12 units. The root sees children limits 10 (from node 2) and 12 (from node 3), total 22, but its own capacity is 15, so the final answer is min(15,22)=15.
Constraints
- 1 <= n <= 200000
- 0 <= c[i] <= 10^9
- The given edges form a tree rooted at node 1
Optimal Approach & Strategy
Use a post‑order DFS to compute for each node the maximum flow it can support as min(capacity, sum of children’s flows), yielding a linear‑time solution.
Brute Force Approach
Try every possible way to split cargo among children recursively, checking capacities at each node, which leads to exponential combinations.
Verified Code Solutions
/**
* @param {number} n - Number of nodes.
* @param {number[]} capacities - Array of node capacities (1-indexed).
* @param {number[][]} edges - Array of edges [parent, child].
* @return {number} Maximum cargo amount.
*/
function solve(n, capacities, edges) {
if (n === 0) return 0;
const adj = Array.from({ length: n + 1 }, () => []);
for (const [u, v] of edges) {
adj[u].push(v);
}
const maxCargo = new Array(n + 1).fill(0);
const dfs = (node) => {
if (adj[node].length === 0) {
maxCargo[node] = capacities[node];
return;
}
let sumChildren = 0;
for (const child of adj[node]) {
dfs(child);
sumChildren += maxCargo[child];
}
maxCargo[node] = Math.min(capacities[node], sumChildren);
};
dfs(1);
return maxCargo[1];
}
// Driver code
const fs = require('fs');
const input = fs.readFileSync(0, 'utf8').split(/\s+/).map(Number);
let idx = 0;
const n = input[idx++];
const capacities = [0];
for (let i = 0; i < n; i++) {
capacities.push(input[idx++]);
}
const edges = [];
for (let i = 0; i < n - 1; i++) {
const u = input[idx++];
const v = input[idx++];
edges.push([u, v]);
}
console.log(solve(n, capacities, edges));#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
typedef long long ll;
ll solve(int n, vector<ll>& capacities, vector<pair<int, int>>& edges) {
if (n == 0) return 0;
vector<vector<int>> adj(n + 1);
for (auto& e : edges) {
int u = e.first, v = e.second;
adj[u].push_back(v);
}
vector<ll> maxCargo(n + 1, 0);
function<void(int)> dfs = [&](int node) {
if (adj[node].empty()) {
maxCargo[node] = capacities[node];
return;
}
ll sumChildren = 0;
for (int child : adj[node]) {
dfs(child);
sumChildren += maxCargo[child];
}
maxCargo[node] = min(capacities[node], sumChildren);
};
dfs(1);
return maxCargo[1];
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
if (!(cin >> n)) return 0;
vector<ll> capacities(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> capacities[i];
}
vector<pair<int, int>> edges;
for (int i = 0; i < n - 1; ++i) {
int u, v;
cin >> u >> v;
edges.push_back({u, v});
}
cout << solve(n, capacities, edges) << endl;
return 0;
}import java.util.*;
import java.io.*;
public class Main {
public static long solve(int n, long[] capacities, int[][] edges) {
if (n == 0) return 0L;
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i <= n; i++) {
adj.add(new ArrayList<>());
}
for (int[] e : edges) {
adj.get(e[0]).add(e[1]);
}
long[] maxCargo = new long[n + 1];
dfs(1, adj, capacities, maxCargo);
return maxCargo[1];
}
private static void dfs(int node, List<List<Integer>> adj, long[] capacities, long[] maxCargo) {
if (adj.get(node).isEmpty()) {
maxCargo[node] = capacities[node];
return;
}
long sumChildren = 0;
for (int child : adj.get(node)) {
dfs(child, adj, capacities, maxCargo);
sumChildren += maxCargo[child];
}
maxCargo[node] = Math.min(capacities[node], sumChildren);
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
long[] capacities = new long[n + 1];
for (int i = 1; i <= n; i++) {
capacities[i] = Long.parseLong(st.nextToken());
}
int[][] edges = new int[n - 1][2];
for (int i = 0; i < n - 1; i++) {
st = new StringTokenizer(br.readLine());
edges[i][0] = Integer.parseInt(st.nextToken());
edges[i][1] = Integer.parseInt(st.nextToken());
}
System.out.println(solve(n, capacities, edges));
}
}def solve(n, capacities, edges):
if n == 0:
return 0
adj = [[] for _ in range(n + 1)]
for u, v in edges:
adj[u].append(v)
maxCargo = [0] * (n + 1)
def dfs(node):
if not adj[node]:
maxCargo[node] = capacities[node]
return
sumChildren = 0
for child in adj[node]:
dfs(child)
sumChildren += maxCargo[child]
maxCargo[node] = min(capacities[node], sumChildren)
dfs(1)
return maxCargo[1]
if __name__ == "__main__":
import sys
input_data = sys.stdin.read().split()
idx = 0
n = int(input_data[idx]); idx += 1
capacities = [0] + [int(input_data[idx + i]) for i in range(n)]; idx += n
edges = []
for _ in range(n - 1):
u = int(input_data[idx]); idx += 1
v = int(input_data[idx]); idx += 1
edges.append((u, v))
print(solve(n, capacities, edges))/**
* @param {number} n - Number of nodes.
* @param {number[]} capacities - Array of node capacities (1-indexed).
* @param {number[][]} edges - Array of edges [parent, child].
* @return {number} Maximum cargo amount.
*/
function solve(n, capacities, edges) {
if (n === 0) return 0;
const adj = Array.from({ length: n + 1 }, () => []);
for (const [u, v] of edges) {
adj[u].push(v);
}
const maxCargo = new Array(n + 1).fill(0);
const dfs = (node) => {
if (adj[node].length === 0) {
maxCargo[node] = capacities[node];
return;
}
let sumChildren = 0;
for (const child of adj[node]) {
dfs(child);
sumChildren += maxCargo[child];
}
maxCargo[node] = Math.min(capacities[node], sumChildren);
};
dfs(1);
return maxCargo[1];
}
// Driver code
const fs = require('fs');
const input = fs.readFileSync(0, 'utf8').split(/\s+/).map(Number);
let idx = 0;
const n = input[idx++];
const capacities = [0];
for (let i = 0; i < n; i++) {
capacities.push(input[idx++]);
}
const edges = [];
for (let i = 0; i < n - 1; i++) {
const u = input[idx++];
const v = input[idx++];
edges.push([u, v]);
}
console.log(solve(n, capacities, edges));Asked in Top Tech Interviews
Solve in Interative Editor
Ready to test your code? Open our built-in compiler, run custom test suites, and see detailed complexity analysis reports instantly.