CS494 Lecture Notes - Problem Solving with Network Flow


This lecture highlights four problems from Topcoder that have network flow solutions. All of them rely on minimum cuts in some way for their solutions. In my opinion they are pretty subtle and fascinating.

#1: Dealing with Huge Edge Capacities: SRM 632, D1, 500-pointer - CandyCupRunningCompetition

As with all of these problems, there is a general principle at work. In this problem, the general principle is the following:

If the relative ordering of edge weights will never change, then you can simplify residual graph processing significantly. The structure of your simplified residual graph will match the structure of the unsimplified residual graph, and you can use the minimum cut to determine the maximum flow.

Although the writeup of this Topcoder question is cute, it is a network flow problem, plain and simple. You are given a graph with N nodes ane M edges. The edges are numbered 0 through (M-1), and the capacity of edge i is 3i. Your goal is to find the maximum flow from node 0 to node N-1. There's a little subtlety that the edges are bidirectional, which means that you really have two edges between any pair of nodes. The edges share their "candy," though, so again, it's a standard network flow problem that you are solving.

The problem is difficult, because the constraints have M being as big as 2000, and 31999 is not going to fit into a long long.

What I do in these situations is work through some examples to get some inspiration. Let's use the following example graph:

It should be pretty clear that the total flow through this graph is 1+9+81 = 91. Let's process paths through this graph. Start with 0-1-2-3-4, with a flow of one. Here's the residual (I'm showing the forward-edge-weight/backward-edge-weight):

Now, let's process 0-2-3-4 with a flow of nine:

And finally 0-3-4 with a flow of 81:

The minimum cut here is equal to the flow -- edges (1-2), (0-2) and (0-3), whose weights sum to 91.

Suppose you have an edge with a value of 3i, for example, 27. How much flow can go through that edge? Well, obviously, 0 and 27. What about smaller values? Those would be 9, 3, 1 and their combinations. That's a max of 13. You'll note that 13 is less than 27/2. This turns out to be true in all cases -- the amount of flow that can go through an edge whose value started at 3i is either exactly 3i, or it is less than 3i/2.

Does that help us? A little. Consider an augmenting path through the residual graph. I will propose that the flow through this path is going to be equal to the edge whose initial weight was the smallest of all the edges on the path. This is regardless of what has happened previously to that edge.

Think about it -- suppose you have an augmenting path that includes the edge whose weight started at 27, and that is the smallest value of initial weight for all edges on the path. Then, the flow through the path will be equal to the flow through that edge.

Let's elaborate more. Supoose that edge has been part of other augmenting paths, and it has had 9, 3 and 1 added to it. Its weight is now 40. The smallest edge whose weight was bigger than 27 is 81, and the only edges that could have been subtracted from it are 27, 9, 3, and 1, which would give it a weight of 41. This means that the edge whose weight started at 81 will always be bigger than the edge whose weight started at 27, regardless of the flows that have been added to them or subtracted from them. You can generalize this, but I won't.

What's the upshot of this? That you only need to keep track of the initial values of the edges, and when you subtract a smaller edge from a larger one, or add a smaller edge to a larger one, you don't have to do the addition or subtraction. You will end up with the same residual graph as you would were you actually doing the addition and subtraction.

Let's work through the example above. We first process the path 0-1-2-3-4, and we don't bother with addition, or subtraction of smaller values from larger. I know the graph looks weird, but it has the same structure as the residual graph that we first made:

Compare this graph to the first residual in the example above, and see how it matches. In particular, if you ignore their weights, then the two graphs are the same. In other words, this graph has the same structure as the original residual graph. That is going to be true for each time we process a path. Here's 0-2-3-4:

And the final path: 0-3-4:

We're done. As before, go ahead and compare the final residual above to this one -- you should see how they match. Because the two graphs have the same structure, we can use either of them to find the minimum cut in the original graph. So, by using the second version, which required no addition and subtraction, we end up with a residual graph that we can use to find the minimum cut in the original graph, and the sum of the edges in the minimum cut equals the maximum flow!

We really should do a formal proof of this, but I'm not going to -- I'll leave that to the motivated student. When we program it, what we'll do is represent each edge's weight by its logarithm base three (which is just its index in the input vector). That will be a number between 0 and 1999. No more worries about long long's!

We can use the Edmonds-Karp algorithm to find augmenting paths, and then when we process the residual graph, we don't bother adding small values to big values, or subtracting small values from big values. We simply use the big values.

When we're done, and we can no longer find any augmenting paths, then we use the residual to find the minimum cut in the original graph. The sum of those edges is our answer!

Let's go ahead and do final example from the problem specification to help reinforce. This is example 4. Here's the graph where edge weights are represented by their logarithms base three:

If we use Edmonds-Karp, there are three paths that we can choose first (with three edges). Let's do 0-1-4-5. Our residual simply removes the forward edge [0-1]:

Next, we have 0-2-3-5. The residual simply removes the forward edge [2-3]:

The shortest hop path on the graph is now 0-2-1-3-5. To process the residual, we remove the edge [1-3]:

There are still a few paths through the graph. The shortest hop one is 0-2-1-4-5 whose limiting edge is [2-1]:

Now there are no augmenting paths through the graph. We perform the DFS to find nodes reachable from the source in the final residual graph. Those are nodes 0 and 2. Our minimum cut is then the edges in the original graph that go from 0 or 2 to any other nodes. Those are edges [0-1], [2-1] and [2-3]. Their weights are 3, 9 and 27, so our final answer is 39 (which matches the correct one).

You should note that we had to use the minimum cut to find our answer. Were we to have used our flow calculations processing the augmenting paths, we would have come up with an incorrect answer of 40. Why? Because we added one unit of flow when we processed 0-2-1-3-5, and then three units when we processed 0-2-1-4-5; however, in reality, that last path only added two units of flow. We ignored this fact, because the residual graphs that remained were the same as the correct residual, and that means that we could use it to calculate the correct minimum cut, even though we calculated the incorrect flow. That's pretty cool, isn't it?


#2: Partitions with benefits: SRM 653, D1, 450-pointer - Singing

This topcoder problem is one of a class of problems that don't look like maximum flow problems, but indeed are. These problems follow a pattern:

You have a collection of elements, and a weight on various pairs of elements. Your job it to partition the elements into two sets such that:
  • You can only include the weight between two elements if they are in the same partition.
  • Your job is to determine a way to partition the elements to either maximize the weights that are included, or minimize the weights that are not included.
This problem may be solved with network flow.

Let's try an example that has a topcoder-esque flavor.

This fits the above description exactly. The "weight" between two elements is the amount of gold in their shared safety deposit boxes, and the partitioning is done by the spaceships.

To solve this type of problem, you turn it into a graph, with elements of the population as nodes, and weights as edges. You then define a source and a sink (this is typically obvious to do from the problem definition), and find the minimum cut in the graph. Those are the edges excluded. The other edges are the ones included, and by determining the minimum cut, you have maximized the edges that are included.

Let's do an example with the above "Thortopia" problem. Here are the safety deposit boxes:

The answer becomes pretty obvious when you create a graph from the problem:

With 0 as a source and 1 as a sink, it's pretty easy to see that the maximum flow is 1009, and that the minimum cut is composed of the red edges: (0-1), (1-2), (2-4) and (3-5). Therefore, the amount of gold that leaves the planet is the sum of the remaining edges: 12+15+7+9+8 = 51.

Armed with this information, let's take a look at the Topcoder problem. I'll summarize it below in terms of numbers and sets. You are given four input values: The numbers N, Low and High and a vector P of numbers between 1 and N.

You are going to partition the numbers from 1 to N into two sets, A (for Alice) and B (for Bob). The numbers from 1 to Low-1 must go into set B and the numbers from High+1 to N must go into set A. The numbers from Low to High can go into either set.

You want to assign the numbers so that the number of adjacent numbers in the vector P that belong to different sets is minimized.

Let's take example three from the Topcoder problem. In this example, N is 10, Low is 4, and High is 5. The vector P is {1, 4, 3, 5, 2, 5, 7, 5, 9}. The only unassigned numbers are four and five. We can enumerate their potential assignments, and we'll see that the smallest number of adjacent numbers in different sets is three:

Of course, N, Low and High can range up to 1000, so an enumeration solution isn't going to work in general, but it should help you read the problem.

Now, suppose we want to turn this into a partitioning problem like Thortopia above. Obviously, the elements that we are partitioning are the numbers, and some of them are going to be assigned to sets unconditionally (the low and high numbers). We associate an edge between two numbers if they are adjacent in P, and the weight we associate with the edge is the number of times that they are adjacent in P. We want to partition the numbers and minimize the weights of the edges that span partitions. Voila -- problem solved -- this fits our paradigm exactly!

There is a slight issue with how we handle the numbers from 1 to Low-1, and from High+1 to N. One very simple way is to change P so that all numbers from 1 to Low-1 are replaced with the number 0, and all numbers from High+1 to N are replaced with N+1. Example 3 would have P become: { 0,4,0,5,0,5,11,5,11 }. Then, the nodes 0 and N+1 will be the source and sink in our graph calcaultion. Here's the graph for example 3:

It's pretty easy to see that the maximum flow from 0 to 11 is 3, as is the minimum cut. Hence the answer of 3.

I'm going to describe another way of handling the low and high numbers, because it's good for you to see this trick. We're going to have a source node labeled B and a sink node labeled A. We don't modify P, but leave all of the numbers as they are. And we add weights of infinite size between all low numbers and B, and between all high numbers and A. Do you see how that forces the low numbers into B and the high numbers into A? Here's the new graph for example 3:

It's pretty clear that the maximum flow through this graph is three, and the minimum cut is either the two red edges or the two blue edges.

Just for yucks, here's the graph for example 4, with the cut edges colored red. (Unlabeled edges have weights of one).


#3: More partitions with benefits: SRM 634, D1, 500-pointer - SegmentDrawing

A more general problem

You have two distinct sets of items, A and B. Each item in the two sets has a positive weight, and you want to select a collection of items from the two sets that maximizes their weight, subject to constraints of a particular form.

The constraints are of the form: "If you choose this item from set A, then you can't choose these items from set B," and, conversely, "If you choose this item from set B, then you can't choose these items from set A."

You may solve this problem with network flow.

One way to solve this problem is to model it as a graph for network flow. There is a source A and a sink B, and nodes for every element in each set. The elements in set A have edges from node A with weights equal to their weight, and the elements in set B have edges to node B with weights equal to their weight. Finally, for every constraint involving an item in set A and and item in set B, there is an edge of infinite capacity from the node from set A to the node from set B.

Now, determine the minimum cut of the graph. It will have to involve only edges from A or to B. The nodes corresponding to those edges are not in our optimal set. The remaining nodes are.

Why does this work? Let's try a simple example. Suppose A = { c, d, e } and B = { f, g, h }, and weights are assigned as follows:

(c=16, d=5, e=4, f=8, g=7, h=11)

Suppose I want to choose a collection of elements from these sets such that the collection has maximum weight, but there are no constraints. Obviously, I just choose every element. However, I can model that as a graph as described above, with no edges from the A nodes to the B nodes:

It should be clear that there is no flow from A to B, so the minimum cut is composed of the empty set. Therefore, my collection is composed of all of the nodes.

Let's try a harder example. We'll use the same sets and weights as above, but now let's constrain it so that if you include any node from A, then you can't include any node from B. Again, we don't need network flow to solve this -- simply sum up the weights from A and the weights from B, and whichever is larger, you use all of them. But we can model it with a graph defined as above, where all of those thick edges have infinite capacity:

Here's the flow graph, with the edges on the minimum cut colored green.

This means that the elements of A are excluded, and our collection is composed of all of the elements of B: { f, g, h }. Of course, those three items have a greater sum than the elements of A.

Now, let's look another example, which is slightly more interesting. Let's suppose that:

Again, you don't need network flow to solve this. Simply take the greater of each pair: c, g and h. However, we can model it as a graph:

It should be clear that the max flow through this graph has three paths of flow 8, 5 and 4. Therefore, the min cut is composed of the green edges: (A,d),(A,e),(f,B), meaning those three nodes (d,e,f) are not in our collection. The other three (c,g,h) are. Nice.

And an even more interesting example. Let's add three more constraints to the last example:

Now it's not so simple to eyeball the weights and constraints. However, the graph will help us:

Here's the flow graph. I've drawn the min-cut edges in green:

That means that our collection consists only of the nodes c and h. As it turns out, that is the correct answer -- you can't come up with a better collection that satisfies the constraints.

SegmentDrawing

Armed with this information, we can solve the Topcoder problem, which I summarize as follows: You have N points on a plane, each with specified (x,y) values. You are going to partition the points into two sets, Red and Blue to solve a maximization problem. Each pair of points has two score values -- RedScore and BlueScore. You score RedScore(a,b) when two points a and b are both in the Red set, and you connect them with a line segment. Similarly, you score BlueScore(a,b) when two points a and b are both in the Blue set, and you connect them with a line segment. There is a final constraint -- line segments from different sets cannot intersect. The input guarantees that no three points are co-linear.

Let's illustrate this with Example zero, which has four points: (0,1), (1,0), (0,-1) and (-1,0). I'll draw them on a graph and label their scores as colored edges:

The solution here is pretty easy -- simply color all of the points blue, and you get to sum up all of the blue edges: 27. The solution also helps you think about the problem. How can you partition the points into two sets legally so that you get to count scores from each set? Well, if I color points 0 and 1 red, and I want to include a blue score, I'll have to color points 2 and 3 blue. The only scores I would keep are the red score for (0-1), and the blue score for (2-3). The following input would yield that solution:

Let's call that "Example -1", since that is not an example from the topcoder specification, but we'll be referring to it later.

Now, let's formulate the problem in terms of the partitioning problem above. Given a set with N points, there are going to be exactly (N)(N-1)/2 red edges and (N)(N-1)/2 blue edges. These edges are going to be the elements that we partition in our general problem. You'll note, each edge has a weight. And we are selecting edges from each partition subject to the constraint that no edges from different partitions intersect. And we want to maximize the total weight of the selected edges.

Below, I'm redrawing the graph from Examples 0 and -1, but I'm simply labeling the edges, and not drawing the weights:

Suppose you select red edge 01 to be in your set. Then it should be clear that you cannot select blue edges 01, 02, 03, 12 and 13. However, you can select edge 23. On the other hand, if you select red edge 02, then you can't select any blue edge.

Every edge has constraints on the edges from the other set. Specifically, red edge AB excludes every blue edge with endpoints A or B, and every blue edge that intersects AB.

We now know enough to use the the network flow technique from the above general problem formulation to solve the problem. Here's the network flow graph for Example 0:

(The black edges have infinite capacity).

It isn't easy to figure out the flow here, but it ends up saturating all of the red edges, which means that they compose the minimum cut, and our answer is the sum of all of the blue edges. In some respects Example -1 is easier to see. The graph has the same structure as example 0, but different weights:

You can process augmenting paths by always including the edges with weight 100 -- eventually the flow will saturate all of the other edges, yielding a maximum flow of (3+1+5+6+4)+(3+7+2+5+4) = 41. The minimum cut will be composed of the saturated edges, and the answer will be the sum of the remaining edges: 200.

I'm afraid that the other examples will be too complex to draw; however, I'm hoping that the above examples have helped you figure out how to code up the solution!


#4: I'm glad no one put a gun to my head to solve this problem: SRM 633, D1, 600-pointer - DoubleTree

This is a hard problem. My attempt was to solve it with Dynamic Programming, which I did, but it choked on the larger problems. I looked up the solution online, and let's just say, it took a while for me to understand it. I'm glad my livelihood did not depend on coming up with the solution to this problem.

As with the problems above, there's a more general problem that seems quite unrelated to network flow that you can solve with network flow. Actually, in this case, the general problem seems unrelated to the Topcoder problem! We'll get to that. The problem is this:

You have a collection of elements with weights, which may be positive or negative numbers. You want to choose a subset of these elements such that their total weight is maximized.

There are constraints to make your life difficult, which are of the form, "If you include element X in your subset, then you must include element Y too." Let's abbreviate that X → Y.

This problem may be solved with network flow.

As always, an example or two never hurts, so let's do a few:

Example 4A
Element 0: Weight = 50
Element 1: Weight = -100
Example 4B
Element 0: Weight = 50
Element 1: Weight = -100
0 → 1
Example 4C
Element 0: Weight = 100
Element 1: Weight = -50
0 → 1
Answer: { 0 } Answer: { } Answer: { 0, 1 }

The conversion to network flow is a little confusing. First, choose a number bigger than the maximum positive value. Call that number B. You are going to have a source node S and a sink node T, plus an node for every element in the set.

You have an edge from S to each node, whose weight is B, and you have an edge from each node to T, whose weight is B-weight. For each constraint of the form X → Y, you will have an edge from X to Y with infinite weight.

Determine the maximum flow from S to T, and the minimum cut of the graph. If an edge of the form (X,T) is in the minimum cut, then X should be included in the subset that answers the problem. Moreover, the weight of that subset will be NB - Flow, where N is the number of elements in the set.

Let's explore how this works. First, take a look at the graph for example 4A:

I've colored the nodes that are in the minimum cut red.

It should be clear that there is an augmenting path through each element's node. The flow through that path is B, if the element's weight is negative, and B-weight if the element's weight is positive. That means that if element X's weight is negative, the edge (S,X) is in the minimum cut, and if it is positive, then (X,T) is in the minimum cut. That's exactly right.

The flow of the graph is going to be NB - (sum of positive weights). If you subtract that from NB, then you get the sum of the positive weights. Nice.

Adding a constraint makes it a little harder. Here are the two graphs for examples 4B and 4C:

4B
4C

Well, it works. But why? This will be a bit of a hand-wavy explanation, but I hope it conveys the flavor. Suppose there is a constraint of the form X → Y. In the flow graph, there will be a certain amount of flow coming into X, which is guaranteed to be at least B. It can be more if there are constraints of the form Z → X. If that flow is less than B-weight, then node X won't be in the set, and the determination of whether node Y is in the set will be up to other factors. Most assuredly, the edge (S,X) will be in the minimum cut. That's pretty easy to understand.

Now, suppose the flow coming into X is greater than B-weight. Then there will be an augmenting path from S to T with flow equal to B-weight. There will also be some excess flow than can go through the edge (X,Y). Either all of that flow will be used for augmenting paths, or it won't.

Look at example 4B above. The augmenting path that goes through (0,T) has B-50 units of flow, so there are 50 units leftover. All of that flow will be used to go through node 1, because the edge (1,T) is 100 units bigger than B.

Converesly, in example 4C, there are 100 units of flow leftover, and they will not all be used for augmenting paths, because only 50 are required to saturate edge (1,T).

So how does that correspond to set inclusion? Well, let's suppose that a node X has a bunch of X → Y constraints, and that it has a few Z → X constraints, as well. And let's suppose that X is going to be included in our set. We'd like to show that all of the Y have to be included in the set as well. We won't be able to determine anything about the Z nodes.

Take a look at this example picture:

Since X is in the set, the edge (X,T) is in the minimum cut. Because of that, we know that (S,X), (S,Z1) and (S,Z2) cannot all be in the minimum cut as well. Why? Because if they were, we wouldn't need (X,T) in the cut, and the cut wouldn't be minimum.

This means that there has to be a path from S to X composed of edges that are not in the minimum cut. For that reason, to cut S off from T, the (Y1,T) edge and the (Y2,T) edge have to be in the minimum cut. That's just what we want. We can't say anything about the Z nodes. That's also just what we want.

The Topcoder Problem

Back to the Topcoder Problem.

The problem is as follows. You have two trees with N nodes. Each node i has a weight w[i], which is the same in both trees. The weight can be negative. Your job is to find a subset of nodes, such that the subset is connected in both trees, and the sum of the weights of the nodes is maximized.

Let's look at an example (example 0 in the topcoder problem):

You can eyeball that one to see that the subtree is composed of nodes 0 and 1. That is a connected component in both trees and its total weight is 1024. You'd like to be able to add node 2 to the mix, but if you do so, you'll have to include node 3, to make the component connected in tree 2. Because node 3's weight is -100, and node 2's weight is only 100, it is not profitable to add both nodes to our final set.

If you change node 2's weight to 200 and node 3's to -100, now your best set is all of the nodes. The total weight is 1124:

How does this map to our set inclusion problem? First, we are going to solve the problem N times. Each time, we are going to affix a different node to be a root node of the tree, and find the largest connected subtree of both trees that have that node be the root. We'll select the best of these.

Now, given a root node, each non-root node has a parent in each of the trees. It may be the same parent (like nodes 1 and 3 above, which have node 0 as their parent when it is the root) or a different parent (like node 2). If a node is going to be in the connected subtree, then its parent (or parents) also have to be in the subtree. See how that fits the set inclusion problem?

Let me illustrate with example 0. Here, the set is composed of elements 0, 1, 2 and 3, with weights 1000, 24, 100 and -200 respectively. Plus, we have the following constraints:

So, we get the following graph, whose minimum cut is labeled in red. That corresponds to the answer, where nodes 0 and 1 are in the best subtree of both trees.

You'll note that the total flow is 4B-1024, so the total weight of the subset is 1024.

If you change node 2's weight to 100 and node 3's weight to -50, then you get the graph below, and you see that all four nodes are in the best subtree.

Now the flow is 4B-1124, so the total weight of the subset is 1124.

Tell me that's not a great problem.....