AlgoMaster Logo

Optimize Water Distribution in a Village

hardFrequencyUpdated September 21, 2026

Understanding the Problem

At first, this doesn't look like a standard graph problem. We have two types of costs: building a well in a house, or laying a pipe between two houses. The challenge is deciding which houses get their own wells and which houses receive water through pipes from other houses.

A virtual node makes both costs comparable. Introduce a node 0 that represents the water source. Building a well in house i becomes an edge from house i to node 0 with cost wells[i - 1], and a pipe between two houses is already an edge. Every cost is now an edge in one graph, and the goal becomes connecting all houses to the water source for the least total cost. That is a Minimum Spanning Tree (MST) problem on n + 1 nodes.

Key Constraints:

  • 2 <= n <= 10^4 and pipes.length <= 10^4 -> Adding n well-edges to the pipe-edges gives at most about 20,000 edges. An O(E log E) algorithm sorts that in well under a millisecond, so MST is fast enough.
  • 0 <= wells[i], cost_j <= 10^5 -> Costs can be zero. A well with cost 0 means free water for that house. An MST uses exactly n edges, so the total cost is at most n * 10^5 = 10^4 * 10^5 = 10^9, which fits in a signed 32-bit integer (max about 2.1 * 10^9). A plain int accumulator is safe here.

Approach 1: Kruskal's Algorithm with Union-Find

Intuition

With the virtual node in place, connecting all n houses to the water source at minimum cost is a Minimum Spanning Tree over n + 1 nodes (node 0 plus the houses). Kruskal's algorithm builds that tree directly: sort all edges by cost, then scan from cheapest to most expensive, adding each edge whose two endpoints are not already connected.

The "not already connected" check is what Union-Find (Disjoint Set Union) handles. Each node starts in its own component. Adding an edge merges the two components it joins, and an edge is skipped when both endpoints are in the same component (adding it would form a cycle and waste cost). With path compression and union by rank, both the lookup and the merge run in near-constant amortized time.

Algorithm

  1. Create a list of all edges. For each house i (1 to n), add an edge (0, i, wells[i-1]) representing the cost to build a well.
  2. Add all pipe edges (house1, house2, cost) to the same list.
  3. Sort all edges by cost in ascending order.
  4. Initialize a Union-Find structure with n + 1 nodes (0 through n).
  5. Iterate through sorted edges. For each edge, if the two endpoints are in different components, union them and add the edge cost to the total.
  6. Stop when we've added n edges (an MST over n + 1 nodes has exactly n edges).
  7. Return the total cost.

Visualization and Code

Loading animation...

Kruskal's sorts every edge upfront, even when the MST is complete after the first few. The next approach avoids the global sort by growing the tree from the source and pulling edges out of a min-heap one at a time.

Approach 2: Prim's Algorithm with Priority Queue

Intuition

Prim's algorithm builds the MST a different way. Instead of sorting all edges globally, it grows the tree from a starting node, repeatedly picking the cheapest edge that connects a node already in the tree to a node still outside it. A min-heap keeps track of the candidate edges so the cheapest one is always available at the top.

The same virtual node applies. We start the tree at node 0 (the water source) and grow it outward. Each time a new house joins the tree, we push its edges to unvisited neighbors into the heap. Because the heap orders by cost, every edge we pop is the cheapest one currently crossing from the tree to the outside, which is the edge Prim's needs.

The difference from Kruskal's is that edges enter and leave the heap lazily, so there is no upfront sort of the whole edge list. For the edge counts here, both run in O(E log E) and perform similarly.

Algorithm

  1. Build an adjacency list. For each house i, add an edge (0, i, wells[i-1]). For each pipe, add edges in both directions.
  2. Initialize a min-heap with all edges from node 0 (the well costs).
  3. Mark node 0 as visited. Initialize totalCost = 0 and connected = 0.
  4. While connected < n:
    • Pop the cheapest edge (cost, node) from the heap.
    • If node is already visited, skip it (a cheaper edge already reached it).
    • Otherwise, mark it visited, add cost to totalCost, increment connected.
    • Add all edges from node to unvisited neighbors into the heap.
  5. Return totalCost.

Visualization and Code

Loading animation...