Facebook Pixel

3385. Minimum Time to Break Locks II πŸ”’


Problem Description

Bob is trapped in a dungeon and must break n locks to escape. Each lock requires a specific amount of energy to break, which is represented in an array named strength. The array element strength[i] indicates the energy needed to break the i^th lock.

Bob uses a sword with these properties:

  • The sword's initial energy is 0.
  • The energy increase factor X starts at 1.
  • The sword's energy increases by X every minute.
  • To break lock i, the sword's energy must at least match strength[i].
  • The energy resets to 0 after breaking a lock, and the factor X increases by 1.

The challenge is to determine the minimum time, in minutes, required to break all n locks.

Intuition

The problem can be visualized as a sequence where Bob needs to adjust the energy output of his sword to precisely meet or exceed the specific energy requirements of the locks. Each lock changes the dynamics of the sword's energy generation by increasing factor X after being broken.

The solution can be framed as a flow optimization problem, where we aim to minimize the total time to break all locks while considering the changing factor for energy augmentation. Bob's task can be translated into a network flow issue with minimal cost, where the nodes represent locks and energy factors over time, and the edges represent the flow of energy consumption matched with lock requirements.

We construct a graph that models this scenario:

  • Each lock and energy factor iteration forms nodes in the graph.
  • Directed edges link these nodes with capacities and costs adjusted to represent the energy requirement divided by the current factor X.
  • A minimum cost flow algorithm navigates this graph from a starting node (source) to an ending node (sink), accounting for each lock's energy challenge while minimizing the total time.

This approach utilizes variables like the initial source and capacity constraints to map out feasible paths to quickly resolve the problem mathematically, choosing the most time-efficient sequence of actions for Bob to break all locks.

Learn more about Depth-First Search and Graph patterns.

Solution Approach

The solution uses a Minimum Cost Flow (MCF) graph algorithm to strategically break all locks in the minimum time. Key aspects of this implementation are as follows:

  1. Graph Construction:

    • We represent the scenario with a directed graph where nodes signify locks over several minute iterations.
    • The graph has a structure with 2*n + 2 nodes, where n is the number of locks. Two additional nodes include a source s and a sink t.
  2. Edges and Capacities:

    • An edge with a capacity of 1 and a cost of 0 is added from the source node s to each lock node, representing that one lock can be broken at a time.
    • Each node representing a lock is connected to a corresponding node offset by n (as energy resets after a lock is broken), also with a capacity of 1.
    • Finally, these offset nodes are connected to the sink node t.
  3. Calculating Cost:

    • The cost of each edge from lock nodes to their offsets is calculated by (strength[i] - 1) // (j + 1) + 1 where j starts from 0. This formula calculates the minimum time required for the sword's energy to meet or exceed the lock's strength based on the increasing factor X.
  4. Minimum Cost Flow Calculation:

    • We employ the Min-Cost Flow algorithm to determine the minimum cumulative time required to break all locks when traversing from source s to sink t.
    • The function flow(s, t, n) computes this flow by accommodating the energy requirements against each lock's time constraints, ensuring that the flow (representing time) respects capacity constraints.

This algorithm optimally utilizes an advanced graph-based dynamic programming technique, efficiently solving the minimum time problem using shortest path cost concepts embedded within a flow network.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through an example to illustrate the problem-solving approach for breaking the locks with the minimum time using Minimum Cost Flow.

Consider an example where Bob has to break 3 locks with a strength requirement array strength = [3, 2, 5].

  1. Initial Setup:

    • Locks need energy requirements of 3, 2, and 5 respectively.
  2. Graph Construction:

    • We construct a directed graph with 2 * 3 + 2 = 8 nodes. Nodes represent the initial and offset lock states as well as a source (s) and a sink (t).
    • The source node s connects to lock nodes with edges of capacity 1 to signify the ability to approach each lock once.
  3. Edges and Costs:

    • For each lock at node i, we connect it to its offset node at i + n with capacity 1. The offset node model resets the energy level after breaking a lock.
    • The cost for each lock node i heading towards its offset is computed by ceil(strength[i] / (X_j + 1)), where j is the current round of that lock accounted for in X's increments.
  4. Calculating Minimum Time:

    • For lock 0 with strength 3, the cost to offset would be 3 when X = 1. Lock 1 with strength 2 requires only 2/1 = 2 time when X = 1. Lock 2 requires 5 time or more.
    • All offset nodes point towards the sink node t.
  5. Min-Cost Flow Execution:

    • Implement a Min-Cost Flow algorithm to find the minimum time path from s to t.
    • Here, Bob's initial energy increases by X = 1 every minute across all locks. He moves to break locks in the order that minimizes cumulative time.
    • Breaking the second lock first requires 2 minutes (as the sword's energy has to match or exceed 2), resetting X. The X factor then becomes 2 for subsequent locks incrementing energy output. Therefore, lock 1 takes 2 minutes; after increasing X = 2, lock 0 or lock 2 decision will depend upon cumulative cost/time.

    Using this structured flow, Bob would break all locks in minimized cumulative time, achieving the task optimally.

By this systematic implementation, we efficiently tackled the requirement minimizing Bob's time within the permissioned flow of energy through progressive graph traversals and cost evaluations.

Solution Implementation

1from typing import List, Tuple, Optional, NamedTuple, cast
2from heapq import heappop, heappush
3
4class MCFGraph:
5    class Edge(NamedTuple):
6        src: int   # Source node
7        dst: int   # Destination node
8        cap: int   # Capacity of the edge
9        flow: int  # Flow through the edge
10        cost: int  # Cost of the flow
11
12    class _Edge:
13        def __init__(self, dst: int, cap: int, cost: int) -> None:
14            self.dst = dst              # Destination node
15            self.cap = cap              # Capacity
16            self.cost = cost            # Cost
17            self.rev: Optional[MCFGraph._Edge] = None  # Reverse edge
18
19    def __init__(self, n: int) -> None:
20        self._n = n                                      # Number of nodes
21        self._g: List[List[MCFGraph._Edge]] = [[] for _ in range(n)]  # Adjacency list to hold edges
22        self._edges: List[MCFGraph._Edge] = []           # List to hold all edges for quick access
23
24    def add_edge(self, src: int, dst: int, cap: int, cost: int) -> int:
25        # Add an edge from src to dst with capacity cap and cost
26        assert 0 <= src < self._n
27        assert 0 <= dst < self._n
28        assert 0 <= cap
29        m = len(self._edges)
30      
31        # Create the edge and its reverse edge
32        e = MCFGraph._Edge(dst, cap, cost)
33        re = MCFGraph._Edge(src, 0, -cost)
34      
35        # Link reverse edges
36        e.rev = re
37        re.rev = e
38      
39        # Append the edges to their respective nodes
40        self._g[src].append(e)
41        self._g[dst].append(re)
42      
43        # Maintain a list of edges for easy access
44        self._edges.append(e)
45      
46        return m
47
48    def get_edge(self, i: int) -> Edge:
49        # Retrieve the edge details and construct the user-facing Edge named tuple
50        assert 0 <= i < len(self._edges)
51        e = self._edges[i]
52        re = cast(MCFGraph._Edge, e.rev)
53        return MCFGraph.Edge(re.dst, e.dst, e.cap + re.cap, re.cap, e.cost)
54
55    def edges(self) -> List[Edge]:
56        # Return all edges in the graph as a list of Edge named tuples
57        return [self.get_edge(i) for i in range(len(self._edges))]
58
59    def flow(self, s: int, t: int, flow_limit: Optional[int] = None) -> Tuple[int, int]:
60        # Calculate flow from source s to sink t, with optional flow_limit
61        return self.slope(s, t, flow_limit)[-1]
62
63    def slope(self, s: int, t: int, flow_limit: Optional[int] = None) -> List[Tuple[int, int]]:
64        # Calculate the cost flow slope. Outputs tuples of (flow, cost)
65        assert 0 <= s < self._n
66        assert 0 <= t < self._n
67        assert s != t
68      
69        if flow_limit is None:
70            # If no flow limit, set as the sum of capacities exiting s
71            flow_limit = cast(int, sum(e.cap for e in self._g[s]))
72
73        dual = [0] * self._n                             # Dual costs initialization
74        prev: List[Optional[Tuple[int, MCFGraph._Edge]]] = [None] * self._n
75      
76        # Helper function to refine dual costs using Dijkstra's algorithm
77        def refine_dual() -> bool:
78            pq = [(0, s)]                                # Priority queue for shortest path
79            visited = [False] * self._n
80            dist: List[Optional[int]] = [None] * self._n
81            dist[s] = 0
82          
83            while pq:
84                dist_v, v = heappop(pq)
85              
86                if visited[v]:
87                    continue
88              
89                visited[v] = True
90              
91                if v == t:
92                    break  # Found shortest path to t
93              
94                dual_v = dual[v]
95                for e in self._g[v]:
96                    w = e.dst
97                    if visited[w] or e.cap == 0:
98                        continue
99                    reduced_cost = e.cost - dual[w] + dual_v
100                    new_dist = dist_v + reduced_cost
101                    dist_w = dist[w]
102                    if dist_w is None or new_dist < dist_w:
103                        dist[w] = new_dist
104                        prev[w] = v, e
105                        heappush(pq, (new_dist, w))
106            else:
107                return False  # No path to t
108          
109            dist_t = dist[t]
110            for v in range(self._n):
111                if visited[v]:
112                    dual[v] -= cast(int, dist_t) - cast(int, dist[v])
113            return True
114
115        flow = 0
116        cost = 0
117        prev_cost_per_flow: Optional[int] = None
118        result = [(flow, cost)]
119      
120        while flow < flow_limit:
121            if not refine_dual():
122                break
123            f = flow_limit - flow
124            v = t
125            while prev[v] is not None:
126                u, e = cast(Tuple[int, MCFGraph._Edge], prev[v])
127                f = min(f, e.cap)
128                v = u
129            v = t
130            while prev[v] is not None:
131                u, e = cast(Tuple[int, MCFGraph._Edge], prev[v])
132                e.cap -= f
133                assert e.rev is not None
134                e.rev.cap += f
135                v = u
136            c = -dual[s]
137            flow += f
138            cost += f * c
139            if c == prev_cost_per_flow:
140                result.pop()
141            result.append((flow, cost))
142            prev_cost_per_flow = c
143        return result
144
145
146class Solution:
147    def findMinimumTime(self, a: List[int]) -> int:
148        n = len(a)
149        s = n * 2                            # Source node index
150        t = s + 1                            # Sink node index
151        g = MCFGraph(t + 1)                  # Min-cost flow graph
152
153        # Connecting source to tasks and tasks to jobs
154        for i in range(n):
155            g.add_edge(s, i, 1, 0)
156            g.add_edge(i + n, t, 1, 0)
157          
158            for j in range(n):
159                # Compute cost based on task effort and job position
160                g.add_edge(i, j + n, 1, (a[i] - 1) // (j + 1) + 1)
161
162        # Find minimum time to complete all tasks by computing flow
163        return g.flow(s, t, n)[1]
164
1import java.util.*;
2
3class MCFGraph {
4    // Define an edge with source, destination, capacity, flow, and cost
5    static class Edge {
6        int src, dst, cap, flow, cost;
7
8        Edge(int src, int dst, int cap, int flow, int cost) {
9            this.src = src;
10            this.dst = dst;
11            this.cap = cap;
12            this.flow = flow;
13            this.cost = cost;
14        }
15    }
16
17    // Internal edge representation with a destination, capacity, cost, and reverse edge
18    static class _Edge {
19        int dst, cap, cost;
20        _Edge rev;
21
22        _Edge(int dst, int cap, int cost) {
23            this.dst = dst;
24            this.cap = cap;
25            this.cost = cost;
26        }
27    }
28
29    private int numVertices;
30    private List<List<_Edge>> graph;
31    private List<_Edge> edges;
32
33    public MCFGraph(int numVertices) {
34        this.numVertices = numVertices;
35        this.graph = new ArrayList<>();
36        this.edges = new ArrayList<>();
37        for (int i = 0; i < numVertices; i++) {
38            graph.add(new ArrayList<>());
39        }
40    }
41
42    // Add an edge from src to dst with the given capacity and cost
43    public int addEdge(int src, int dst, int cap, int cost) {
44        assert (0 <= src && src < numVertices);
45        assert (0 <= dst && dst < numVertices);
46        assert (cap >= 0);
47
48        int m = edges.size();
49        _Edge edge = new _Edge(dst, cap, cost);
50        _Edge revEdge = new _Edge(src, 0, -cost);
51        edge.rev = revEdge;
52        revEdge.rev = edge;
53
54        graph.get(src).add(edge);
55        graph.get(dst).add(revEdge);
56        edges.add(edge);
57        return m;
58    }
59
60    // Get edge details including source, destination, capacity, and cost
61    public Edge getEdge(int i) {
62        assert (0 <= i && i < edges.size());
63        _Edge e = edges.get(i);
64        _Edge rev = e.rev;
65        return new Edge(rev.dst, e.dst, e.cap + rev.cap, rev.cap, e.cost);
66    }
67
68    // Retrieve a list of all edges in the graph
69    public List<Edge> edges() {
70        List<Edge> result = new ArrayList<>();
71        for (int i = 0; i < edges.size(); i++) {
72            result.add(getEdge(i));
73        }
74        return result;
75    }
76
77    // Compute the flow from source to target with an optional flow limit
78    public int[] flow(int src, int dst, Integer flowLimit) {
79        List<int[]> result = slope(src, dst, flowLimit);
80        return result.get(result.size() - 1);
81    }
82
83    // Implementation of the slope algorithm for min-cost max flow
84    public List<int[]> slope(int src, int dst, Integer flowLimit) {
85        assert (0 <= src && src < numVertices);
86        assert (0 <= dst && dst < numVertices);
87        assert (src != dst);
88
89        // If flow limit is not provided, assume maximum possible flow
90        if (flowLimit == null) {
91            flowLimit = graph.get(src).stream().mapToInt(e -> e.cap).sum();
92        }
93
94        int[] duals = new int[numVertices];
95        Tuple[] prev = new Tuple[numVertices];
96
97        List<int[]> result = new ArrayList<>();
98        result.add(new int[]{0, 0});
99
100        while (true) {
101            if (!refineDual(src, dst, duals, prev)) {
102                break;
103            }
104
105            int f = flowLimit;
106            int v = dst;
107
108            // Calculate the path flow
109            while (prev[v] != null) {
110                Tuple tuple = prev[v];
111                int u = tuple.first;
112                _Edge edge = tuple.second;
113                f = Math.min(f, edge.cap);
114                v = u;
115            }
116
117            // Adjust the flow along the path
118            v = dst;
119            while (prev[v] != null) {
120                Tuple tuple = prev[v];
121                int u = tuple.first;
122                _Edge edge = tuple.second;
123                edge.cap -= f;
124                edge.rev.cap += f;
125                v = u;
126            }
127
128            int c = -duals[src];
129            result.add(new int[]{
130                    result.get(result.size() - 1)[0] + f,
131                    result.get(result.size() - 1)[1] + f * c
132            });
133
134            if (c == result.get(result.size() - 2)[1]) {
135                result.remove(result.size() - 2);
136            }
137        }
138
139        return result;
140    }
141
142    // Use Dijkstra-like algorithm to update dual potentials
143    private boolean refineDual(int src, int dst, int[] dual, Tuple[] prev) {
144        PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
145        pq.add(new int[]{0, src});
146        boolean[] visited = new boolean[numVertices];
147        Integer[] dist = new Integer[numVertices];
148        Arrays.fill(dist, null);
149        dist[src] = 0;
150
151        while (!pq.isEmpty()) {
152            int[] current = pq.poll();
153            int currentDist = current[0];
154            int v = current[1];
155
156            if (visited[v]) continue;
157            visited[v] = true;
158
159            if (v == dst) break;
160
161            int dualV = dual[v];
162
163            // Relax edges
164            for (_Edge edge : graph.get(v)) {
165                int w = edge.dst;
166
167                if (visited[w] || edge.cap == 0) continue;
168
169                int reducedCost = edge.cost - dual[w] + dualV;
170                int newDist = currentDist + reducedCost;
171                Integer distW = dist[w];
172
173                if (distW == null || newDist < distW) {
174                    dist[w] = newDist;
175                    prev[w] = new Tuple(v, edge);
176                    pq.add(new int[]{newDist, w});
177                }
178            }
179        }
180
181        if (!visited[dst]) return false;
182
183        // Update duals after settling at target
184        int distT = dist[dst];
185        for (int v = 0; v < numVertices; v++) {
186            if (visited[v]) {
187                dual[v] -= distT - dist[v];
188            }
189        }
190
191        return true;
192    }
193
194    // Helper class to store pairs of node and edge
195    static class Tuple {
196        int first;
197        _Edge second;
198
199        Tuple(int first, _Edge second) {
200            this.first = first;
201            this.second = second;
202        }
203    }
204}
205
206class Solution {
207    public int findMinimumTime(int[] strength) {
208        int n = strength.length;
209        int source = n * 2;
210        int target = source + 1;
211        MCFGraph graph = new MCFGraph(target + 1);
212
213        // Add edges from the source to each worker and from each job to the target
214        for (int i = 0; i < n; i++) {
215            graph.addEdge(source, i, 1, 0);
216            graph.addEdge(i + n, target, 1, 0);
217
218            // Add edges from each worker to each job with cost determined by the time taken
219            for (int j = 0; j < n; j++) {
220                graph.addEdge(i, j + n, 1, (strength[i] - 1) / (j + 1) + 1);
221            }
222        }
223
224        // Compute min-cost max flow and return the total minimum cost
225        return graph.flow(source, target, n)[1];
226    }
227}
228
1#include <bits/stdc++.h>
2
3using namespace std;
4
5// Define an edge with source, destination, capacity, flow, and cost
6struct Edge {
7    int src, dst, cap, flow, cost;
8
9    Edge(int src, int dst, int cap, int flow, int cost)
10        : src(src), dst(dst), cap(cap), flow(flow), cost(cost) {}
11};
12
13// Internal edge representation with a destination, capacity, cost, and reverse edge
14struct _Edge {
15    int dst, cap, cost;
16    _Edge* rev;
17
18    _Edge(int dst, int cap, int cost) 
19        : dst(dst), cap(cap), cost(cost), rev(nullptr) {}
20};
21
22class MCFGraph {
23private:
24    int numVertices;
25    vector<vector<_Edge*>> graph;
26    vector<_Edge> edges;
27
28public:
29    MCFGraph(int numVertices)
30        : numVertices(numVertices), graph(numVertices) {}
31
32    // Add an edge from src to dst with the given capacity and cost
33    int addEdge(int src, int dst, int cap, int cost) {
34        assert(0 <= src && src < numVertices);
35        assert(0 <= dst && dst < numVertices);
36        assert(cap >= 0);
37
38        _Edge forward(dst, cap, cost);
39        _Edge backward(src, 0, -cost);
40
41        forward.rev = &backward;
42        backward.rev = &forward;
43
44        graph[src].push_back(&edges.emplace_back(dst, cap, cost));
45        graph[dst].push_back(&edges.emplace_back(src, 0, -cost));
46
47        return static_cast<int>(edges.size()) - 2;
48    }
49
50    // Get edge details including source, destination, capacity, and cost
51    Edge getEdge(int i) const {
52        assert(0 <= i && i < edges.size());
53        const _Edge* e = &edges[i];
54        const _Edge* rev = e->rev;
55        return Edge(rev->dst, e->dst, e->cap + rev->cap, rev->cap, e->cost);
56    }
57
58    // Retrieve a list of all edges in the graph
59    vector<Edge> edgesList() const {
60        vector<Edge> result;
61        for (int i = 0; i < edges.size(); i++) {
62            result.push_back(getEdge(i));
63        }
64        return result;
65    }
66
67    // Compute the flow from source to target with an optional flow limit
68    vector<int> flow(int src, int dst, int flowLimit) {
69        vector<vector<int>> result = slope(src, dst, flowLimit);
70        return result.back();
71    }
72
73    // Implementation of the slope algorithm for min-cost max flow
74    vector<vector<int>> slope(int src, int dst, int flowLimit) {
75        assert(0 <= src && src < numVertices);
76        assert(0 <= dst && dst < numVertices);
77        assert(src != dst);
78
79        // If flow limit is not provided, assume maximum possible flow
80        if (flowLimit == -1) {
81            flowLimit = accumulate(graph[src].begin(), graph[src].end(), 0, [](int cum, _Edge* e) {
82                return cum + e->cap;
83            });
84        }
85
86        vector<int> duals(numVertices);
87        vector<pair<int, _Edge*>> prev(numVertices);
88
89        vector<vector<int>> result;
90        result.push_back({0, 0});
91
92        while (true) {
93            if (!refineDual(src, dst, duals, prev)) {
94                break;
95            }
96
97            int f = flowLimit;
98            int v = dst;
99
100            // Calculate the path flow
101            while (prev[v].second != nullptr) {
102                int u = prev[v].first;
103                _Edge* edge = prev[v].second;
104                f = min(f, edge->cap);
105                v = u;
106            }
107
108            // Adjust the flow along the path
109            v = dst;
110            while (prev[v].second != nullptr) {
111                int u = prev[v].first;
112                _Edge* edge = prev[v].second;
113                edge->cap -= f;
114                edge->rev->cap += f;
115                v = u;
116            }
117
118            int c = -duals[src];
119            result.push_back({result.back()[0] + f, result.back()[1] + f * c});
120
121            if (c == result[result.size() - 2][1]) {
122                result.erase(result.end() - 2);
123            }
124        }
125
126        return result;
127    }
128
129    // Use Dijkstra-like algorithm to update dual potentials
130    bool refineDual(int src, int dst, vector<int>& dual, vector<pair<int, _Edge*>>& prev) {
131        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
132        vector<bool> visited(numVertices, false);
133        vector<int> dist(numVertices, INT_MAX);
134
135        pq.emplace(0, src);
136        dist[src] = 0;
137
138        while (!pq.empty()) {
139            auto [currentDist, v] = pq.top();
140            pq.pop();
141
142            if (visited[v]) continue;
143            visited[v] = true;
144
145            if (v == dst) break;
146
147            int dualV = dual[v];
148
149            // Relax edges
150            for (_Edge* edge : graph[v]) {
151                int w = edge->dst;
152
153                if (visited[w] || edge->cap == 0) continue;
154
155                int reducedCost = edge->cost - dual[w] + dualV;
156                int newDist = currentDist + reducedCost;
157
158                if (dist[w] > newDist) {
159                    dist[w] = newDist;
160                    prev[w] = {v, edge};
161                    pq.emplace(newDist, w);
162                }
163            }
164        }
165
166        if (!visited[dst]) return false;
167
168        // Update duals after settling at target
169        int distT = dist[dst];
170        for (int v = 0; v < numVertices; v++) {
171            if (visited[v]) {
172                dual[v] -= distT - dist[v];
173            }
174        }
175
176        return true;
177    }
178};
179
180class Solution {
181public:
182    int findMinimumTime(vector<int>& strength) {
183        int n = strength.size();
184        int source = n * 2;
185        int target = source + 1;
186        MCFGraph graph(target + 1);
187
188        // Add edges from the source to each worker and from each job to the target
189        for (int i = 0; i < n; i++) {
190            graph.addEdge(source, i, 1, 0);
191            graph.addEdge(i + n, target, 1, 0);
192
193            // Add edges from each worker to each job with cost determined by the time taken
194            for (int j = 0; j < n; j++) {
195                graph.addEdge(i, j + n, 1, (strength[i] - 1) / (j + 1) + 1);
196            }
197        }
198
199        // Compute min-cost max flow and return the total minimum cost
200        return graph.flow(source, target, n)[1];
201    }
202};
203
1// Define an edge with source, destination, capacity, flow, and cost
2interface Edge {
3    src: number;
4    dst: number;
5    cap: number;
6    flow: number;
7    cost: number;
8}
9
10// Internal edge representation with a destination, capacity, cost, and reverse edge
11class _Edge {
12    dst: number;
13    cap: number;
14    cost: number;
15    rev: _Edge | null = null;
16
17    constructor(dst: number, cap: number, cost: number) {
18        this.dst = dst;
19        this.cap = cap;
20        this.cost = cost;
21    }
22}
23
24// Define global variables for the number of vertices, graph, and edges
25let numVertices: number;
26let graph: _Edge[][];
27let edges: _Edge[];
28
29// Initialize the graph with a given number of vertices
30function MCFGraph(vertices: number): void {
31    numVertices = vertices;
32    graph = Array.from({ length: numVertices }, () => []);
33    edges = [];
34}
35
36// Add an edge from src to dst with the given capacity and cost
37function addEdge(src: number, dst: number, cap: number, cost: number): number {
38    if (!(0 <= src && src < numVertices) || !(0 <= dst && dst < numVertices) || cap < 0) {
39        throw new Error("Invalid input");
40    }
41
42    const m = edges.length;
43    const edge = new _Edge(dst, cap, cost);
44    const revEdge = new _Edge(src, 0, -cost);
45    edge.rev = revEdge;
46    revEdge.rev = edge;
47
48    graph[src].push(edge);
49    graph[dst].push(revEdge);
50    edges.push(edge);
51    return m;
52}
53
54// Get edge details including source, destination, capacity, and cost
55function getEdge(i: number): Edge {
56    const e = edges[i];
57    const rev = e.rev!;
58    return { src: rev.dst, dst: e.dst, cap: e.cap + rev.cap, flow: rev.cap, cost: e.cost };
59}
60
61// Retrieve a list of all edges in the graph
62function edgesList(): Edge[] {
63    return edges.map((_, i) => getEdge(i));
64}
65
66// Compute the flow from source to target with an optional flow limit
67function flow(src: number, dst: number, flowLimit: number | null): number[] {
68    const result = slope(src, dst, flowLimit);
69    return result[result.length - 1];
70}
71
72// Implementation of the slope algorithm for min-cost max flow
73function slope(src: number, dst: number, flowLimit: number | null): number[][] {
74    if (!(0 <= src && src < numVertices) || !(0 <= dst && dst < numVertices) || src === dst) {
75        throw new Error("Invalid input");
76    }
77
78    // If flow limit is not provided, assume maximum possible flow
79    if (flowLimit === null) {
80        flowLimit = graph[src].reduce((sum, e) => sum + e.cap, 0);
81    }
82
83    const duals = new Array(numVertices).fill(0);
84    const prev: { first: number; second: _Edge }[] = new Array(numVertices).fill(null);
85
86    const result: number[][] = [];
87    result.push([0, 0]);
88
89    while (true) {
90        if (!refineDual(src, dst, duals, prev)) {
91            break;
92        }
93
94        let f = flowLimit;
95        let v = dst;
96
97        // Calculate the path flow
98        while (prev[v] !== null) {
99            const { first: u, second: edge } = prev[v];
100            f = Math.min(f, edge.cap);
101            v = u;
102        }
103
104        // Adjust the flow along the path
105        v = dst;
106        while (prev[v] !== null) {
107            const { first: u, second: edge } = prev[v];
108            edge.cap -= f;
109            edge.rev!.cap += f;
110            v = u;
111        }
112
113        const c = -duals[src];
114        result.push([
115            result[result.length - 1][0] + f,
116            result[result.length - 1][1] + f * c
117        ]);
118
119        if (c === result[result.length - 2][1]) {
120            result.splice(result.length - 2, 1);
121        }
122    }
123
124    return result;
125}
126
127// Use a Dijkstra-like algorithm to update dual potentials
128function refineDual(src: number, dst: number, duals: number[], prev: any[]): boolean {
129    const pq: [number, number][] = []; // Array of tuples [distance, vertex]
130    pq.push([0, src]);
131
132    const visited = new Array(numVertices).fill(false);
133    const distances: (number | null)[] = new Array(numVertices).fill(null);
134    distances[src] = 0;
135
136    while (pq.length > 0) {
137        const [currentDistance, v] = pq.shift()!;
138        if (visited[v]) continue;
139
140        visited[v] = true;
141        if (v === dst) break;
142
143        const dualV = duals[v];
144
145        // Relax edges
146        for (const edge of graph[v]) {
147            const w = edge.dst;
148
149            if (visited[w] || edge.cap === 0) continue;
150
151            const reducedCost = edge.cost - duals[w] + dualV;
152            const newDistance = currentDistance + reducedCost;
153            const currentDistW = distances[w];
154
155            if (currentDistW === null || newDistance < currentDistW) {
156                distances[w] = newDistance;
157                prev[w] = { first: v, second: edge };
158                pq.push([newDistance, w]);
159            }
160        }
161    }
162
163    if (!visited[dst]) return false;
164
165    // Update duals after finding the optimal target
166    const distT = distances[dst]!;
167    for (let v = 0; v < numVertices; v++) {
168        if (visited[v]) {
169            duals[v] -= distT - distances[v]!;
170        }
171    }
172
173    return true;
174}
175
176// Function to compute the minimum time required based on worker strength
177function findMinimumTime(strength: number[]): number {
178    const n = strength.length;
179    const source = n * 2;
180    const target = source + 1;
181    MCFGraph(target + 1);
182
183    // Add edges from the source to each worker and from each job to the target
184    for (let i = 0; i < n; i++) {
185        addEdge(source, i, 1, 0);
186        addEdge(i + n, target, 1, 0);
187
188        // Add edges from each worker to each job with cost determined by the time taken
189        for (let j = 0; j < n; j++) {
190            addEdge(i, j + n, 1, Math.floor((strength[i] - 1) / (j + 1)) + 1);
191        }
192    }
193
194    // Compute min-cost max flow and return the total minimum cost
195    return flow(source, target, n)[1];
196}
197

Time and Space Complexity

The MCFGraph class implements a minimum-cost flow algorithm, most likely using a form of successive shortest path algorithm, commonly represented as the cycle-canceling algorithm or the successive shortest paths algorithm. The time complexity of this class depends heavily on the number of vertices (n), the number of edges (m), and the maximum flow value (U).

Time Complexity

In each iteration of increasing flow, the algorithm performs Dijkstra's algorithm on the graph, which takes O(m + n \log n) with a priority queue. Given that flow is incremented one unit at a time, and depending on how many times flow augmentations are needed, the upper bound on the number of augmentations can be O(nU), where U is the sum of capacities. Thus, in the worst case, the overall time complexity can be roughly approximated as:

  • O(mnU + n^2U \log n)

Specific for the findMinimumTime problem:

  • n = number of tasks
  • Each task pair introduces up to n additional edges in the worst case.
  • Thus, m = O(n^2) and capacities are 1, making U = n.

Resulting time complexity becomes:

  • O(n * n^2 + n^2 * n \log n) = O(n^3 + n^3 \log n) = O(n^3 \log n)

Space Complexity

The space complexity involves the storage of the graph's adjacency list and other auxiliary data for n vertices and m edges:

  • Graph adjacency list storage requires O(m), where m = O(n^2) due to the way edges are added.
  • Auxiliary data structures like the dual, prev, and distance arrays add up to O(n) space each.

Thus, the overall space complexity for the class is:

  • O(n^2)

For the particular findMinimumTime scenario, with the given logic, these factors determine the computational complexities.

Learn more about how to find time and space complexity quickly.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which data structure is used in a depth first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More