L-R поток

Правка en13, от Noobish_Monk, 2025-07-08 18:02:02

Recently I came across the problem 2046D - For the Emperor!, which reminded me of the existence of L-R flows. I first learned about them a long time ago, but at the time we weren't given any tasks beyond "just implement an L-R flow", so I didn't retain the algorithm and had to relearn it in more detail. Unfortunately, there wasn't a blog on Codeforces that explained everything clearly, so I had to dig through various other resources. I found them here, got a rough idea of the concept, and then had to look up an implementation. I still had some questions about it — it felt a bit like a black box. But at 3 a.m. today, something came to me and I finally understood it all. To save the 5 people who will read my blog from waiting for this insight, I want to explain my understanding of the algorithm here and how to implement it conveniently. (Prerequisites: max flow problem and any algorithm that solves it)

What's even an L-R flow?

In the standard version of the maximum flow problem, we're given a source $$$S$$$, a sink $$$T$$$, and edges of the form $$$(u, v, c)$$$, which represent a directed edge from vertex $$$u$$$ to vertex $$$v$$$ with capacity $$$c$$$. A flow along this edge must satisfy $$$0 \le f \le c$$$. The goal is to find the maximum flow through the network. In this version, we are only given an upper bound on the flow. In the L-R version, each edge also has a lower bound, so edges are given in the form $$$(u, v, l, r)$$$, meaning the flow along the edge must satisfy $$$l \le f \le r$$$. At first glance, this may seem like a strange constraint, but in some problems it arises quite naturally. Let's learn how to solve this version of the max flow problem.

Let me mention right away that in problems involving L-R flows, the network is (almost) always acyclic. This is because you could send flow around a cycle rather than from $$$S$$$, which might technically satisfy the constraints but is mostly meaningless. (At least, I haven't encountered any problems where cycles matter, though such problems might exist.)

To do this, we reduce it to the standard maximum flow problem. Let's say we initially send $$$l$$$ units of flow through every edge. Obviously, this will most likely break the network — some vertices will receive more flow than they send out, and others will demand more flow than they receive. In a way, this is just a standard flow problem — there are two sides, one supplies flow, the other consumes it, and we want to satisfy all demands. To reduce it to that, we'll make the following modification to the network. We introduce an additional source $$$S'$$$ and sink $$$T'$$$, and replace each edge $$$(u, v, l, r)$$$ with the familiar edges $$$(u, v, r - l)$$$, $$$(S', v, l)$$$, and $$$(u, T', l)$$$ (even if one of the endpoints is $$$S$$$ or $$$T$$$).

The idea behind this is as follows: vertex $$$v$$$ receives $$$l$$$ units of flow that it needs to send out, and vertex $$$u$$$ has $$$l$$$ units of flow going out that need to be compensated.

But what's the point of changing the source and sink? How will the flow move from "oversaturated" vertices to "undersaturated" ones? Now I'll make one final modification to the network — add the edge $$$(T, S, \infty)$$$ — and explain why it's needed. Let's look at some edge $$$(u, v, l, r)$$$. I replaced it with $$$(u, v, r — l)$$$, $$$(S', v, l)$$$, and $$$(u, T', l)$$$. That means I've already sent $$$l$$$ units of flow along the edge and added a requirement to somehow return it. But normally, we send flow from $$$S$$$ to $$$T$$$, so this edge should lie on a path from $$$S$$$ to $$$T$$$. Let's try sending flow from $$$S'$$$ to $$$T'$$$. Suppose we find a path $$$S', v, v_1, \ldots, v_k, T, S, v_{k+1}, \ldots, v_m, u, T'$$$ and send $$$l$$$ units of flow along it. Then, essentially, we've completed a path from $$$S$$$ to $$$T$$$ that goes through the edge $$$(u, v)$$$.

The blue edges are the ones through which the flow will pass. That is, we effectively started a flow of $$$l$$$ through the edge, and with this network, we're completing that flow so that it goes from $$$S$$$ to $$$T$$$.

Thus, we need to find a maximum flow from $$$S'$$$ to $$$T'$$$ in order to "fix" the network. More precisely, not a maximum flow, but a flow of a specific value. That value is equal to the sum of all $$$l$$$. But since the flow can't exceed this amount, we're essentially interested in the maximum flow.

Great, we've fixed the network and, in effect, sent the minimum possible flow. Now we want to turn it into a maximum flow. To do that, we can simply run a standard max flow algorithm using the original source and sink — there's no need to add anything at the end. You can find the implementation below.

Implementation of L-R flow

And yet, why don't we need to add anything when we compute the maximum flow from $$$S$$$ to $$$T$$$? After all, our network has capacities $$$r - l$$$, so how do we account for the flow that we've already sent? At this point, let's try to understand what flow we're actually sending when we push $$$l$$$ through all the edges.

Each vertex gets an $$$overflow_v$$$ — how much extra flow it has, or how much it lacks if $$$overflow_v \lt 0$$$. It's important to recall that our network is acyclic, so the flow from $$$S'$$$ to $$$T'$$$ can be decomposed into the following kinds of paths: $$$S', v, T'$$$ and those that go through the edge $$$(T, S)$$$. The first type of path appears when some flow both enters and exits a vertex — these two constraints cancel each other out. As a result, each vertex ends up with $$$|overflow_v|$$$ of "oversaturation" (or "undersaturation"), which must then be routed through the edge $$$(T, S)$$$.

This means that the flow through the edge $$$(T, S)$$$ equals the sum of all positive $$$overflow_v$$$. But how is that accounted for when we run max_flow(S, T)?

Here's how: we have a reverse edge from $$$S$$$ to $$$T$$$, so we can "undo" the flow from $$$T$$$ to $$$S$$$, effectively increasing the result by exactly the amount of the positive $$$overflow_v$$$ that we initially pushed. So everything ties together nicely, and there's no need to keep anything extra in mind — just call max_flow and enjoy life.

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en14 Английский Noobish_Monk 2025-07-08 18:11:00 9
en13 Английский Noobish_Monk 2025-07-08 18:02:02 0 (published)
ru16 Русский Noobish_Monk 2025-07-08 16:10:11 0 (опубликовано)
en12 Английский Noobish_Monk 2025-07-08 16:08:07 2 Tiny change: 'on of the ,ax flow pr' -> 'on of the max flow pr'
ru15 Русский Noobish_Monk 2025-07-08 16:07:18 22
en11 Английский Noobish_Monk 2025-07-08 16:05:44 22
ru14 Русский Noobish_Monk 2025-07-08 16:04:57 2
ru13 Русский Noobish_Monk 2025-07-08 16:04:40 4
ru12 Русский Noobish_Monk 2025-07-08 16:04:14 96
en10 Английский Noobish_Monk 2025-07-08 16:03:50 10 Initial revision for English translation
en9 Английский Noobish_Monk 2025-07-08 16:03:07 47 Initial revision for English translation
en8 Английский Noobish_Monk 2025-07-08 16:02:13 0 Initial revision for English translation
en7 Английский Noobish_Monk 2025-07-08 16:02:02 2 Initial revision for English translation
en6 Английский Noobish_Monk 2025-07-08 16:01:42 15 Initial revision for English translation
en5 Английский Noobish_Monk 2025-07-08 16:00:57 5 Initial revision for English translation
en4 Английский Noobish_Monk 2025-07-08 16:00:21 8 Initial revision for English translation
en3 Английский Noobish_Monk 2025-07-08 16:00:00 3 Initial revision for English translation
en2 Английский Noobish_Monk 2025-07-08 15:59:43 4 Initial revision for English translation
en1 Английский Noobish_Monk 2025-07-08 15:59:26 9319 Initial revision for English translation (saved to drafts)
ru11 Русский Noobish_Monk 2025-07-08 15:44:19 7 Мелкая правка: ' cout << '\n';\' -> ' cout << ans << '\n';\'
ru10 Русский Noobish_Monk 2025-07-08 15:43:20 80
ru9 Русский Noobish_Monk 2025-07-08 15:42:52 1710
ru8 Русский Noobish_Monk 2025-07-08 15:28:13 3233
ru7 Русский Noobish_Monk 2025-07-08 15:26:13 793 Мелкая правка: ' $(u, v)$.' -> ' $(u, v)$.\n\n![ ](https://mirror.codeforces.com/6bd2e0/flow1 (1)-1.png)'
ru6 Русский Noobish_Monk 2025-07-08 14:59:01 75 Мелкая правка: ' $(u, v)$.' -> ' $(u, v)$.\n\n![ ](https://mirror.codeforces.com/6bd2e0/flow1 (1)-1.png)'
ru5 Русский Noobish_Monk 2025-07-08 14:55:37 19 Мелкая правка: ', v)$.\n\n' -> ', v)$.\n\n![ ](flow1 (1))'
ru4 Русский Noobish_Monk 2025-07-08 14:54:56 15 Мелкая правка: ', v)$.\n\n' -> ', v)$.\n\n![ ](flow1 (1))'
ru3 Русский Noobish_Monk 2025-07-08 14:53:07 4 Мелкая правка: ' $(u, v)$.' -> ' $(u, v)$.\n\n'
ru2 Русский Noobish_Monk 2025-07-08 14:50:12 2258
ru1 Русский Noobish_Monk 2025-07-08 14:24:41 858 Первая редакция (сохранено в черновиках)