ICPC 2025 - C. Bride of Pipe Stream
At every station we may split the incoming flow arbitrarily among the outgoing ducts, while each duct then distributes its flow to lower stations and reservoirs by fixed percentages. The graph is acyclic because every...
Source-first archive entry
This page is built from the copied files in competitive_programming/icpc/2025/C-bride-of-pipe-stream. Edit
competitive_programming/icpc/2025/C-bride-of-pipe-stream/solution.tex to update the written solution and
competitive_programming/icpc/2025/C-bride-of-pipe-stream/solution.cpp to update the implementation.
The website does not replace those files with hand-maintained HTML. It reads the copied source tree during the build and exposes the exact files below.
Problem Statement
Copied statement text kept beside the solution archive for direct reference.
Problem C
Bride of Pipe Stream
Time limit: 12 seconds
The story continues! For several years now, your town has been gifted with an abundance of Flubber, the
adorable-but-slightly-flammable-and-toxic-and-acidic-and-sentient-and-mischievous man-made chemi-
cal. The search continues for more (or, well, any) uses for the substance. But in the meantime, the
Flubber factory continues to produce it at full capacity. Efforts to shut it down have failed, partly be-
cause nobody is sure who is actually running the factory.
You’ve been tasked with storing the perpetually-flowing Flubber in various Flubber reservoirs for future
use (or, at least, to get it out of everyone’s hair – literally). To accomplish this, you have access to a
complicated network of Flubber ducts, connecting up various Flubber stations and reservoirs.
Every Flubber station has one or more Flubber ducts leading from it, and has various gates that may
be raised or lowered so that incoming Flubber will drain into the output Flubber ducts in any desired
proportion. For instance, you can send all the Flubber down one duct, or split it between two ducts
25–75, etc.
In contrast, a Flubber duct flows down to one or more lower stations or reservoirs, but the Flubber drains
into them in a fixed proportion that you do not control. It is possible that some of the Flubber is lost to
the environment as well, but that is a problem for your successor, not you.
You would like to fill all the reservoirs as quickly as possible. That is, you want to maximize the
minimum amount of Flubber flowing into any of the reservoirs, among all possible configurations of
station drainage.
Figure C.1 illustrates the two sample inputs. Stations and reservoirs are shown as numbered nodes,
colored green for stations and blue for reservoirs. Ducts are depicted as white nodes. For example, in
the first sample input (left), Flubber can be sent from station 1 in any proportion to its two downstream
ducts, but each duct will distribute its inflow according to the percentages printed on its outgoing edges.
1
1
40%
2
80% 10% 30%
50% 40% 60% 50%
100%
3 4 5 2 3
Figure C.1: Illustrations of the two sample inputs.
Input
The first line of input contains three integers s, r, and d, where s (1 ≤ s ≤ 10 000) is the number of
stations, r (1 ≤ r ≤ 3) is the number of reservoirs, and d (s ≤ d ≤ 20 000) is the number of ducts. The
stations are numbered from 1 to s and the reservoirs are numbered from s + 1 to s + r, in decreasing
order of altitude. The factory’s Flubber initially flows into station 1.
49th ICPC World Championship Problem C: Bride of Pipe Stream © ICPC Foundation 5
Each of the remaining d lines starts with two integers i and n, where i (1 ≤ i ≤ s) is the station that
can drain into this duct, and n (1 ≤ n ≤ 10) is the number of outputs of this duct. The remainder of the
line contains n pairs of integers o and p, where o (i < o ≤ s + r) is a station or reservoir to which this
duct drains, and p (1 ≤ p ≤ 100) is the percentage of the Flubber entering the duct that will drain to o.
The o values for a given duct are distinct. Every station has at least one duct that it can drain into. The
percentages for a given duct’s outputs will sum to at most 100.
Output
Output a single percentage f , which is the highest possible percentage such that, for some configuration
of station drainage, all reservoirs receive at least f % of the factory’s produced Flubber. Your answer
should have an absolute error of at most 10−6 .
Sample Input 1 Sample Output 1
2 3 3 24.0
1 2 3 80 4 10
1 2 2 40 4 30
2 1 5 100
Sample Input 2 Sample Output 2
1 2 3 42.8571428571
1 1 2 50
1 1 3 50
1 2 2 40 3 60
49th ICPC World Championship Problem C: Bride of Pipe Stream © ICPC Foundation 6
Editorial
Rendered from the copied solution.tex file. The original TeX source remains
available below.
Key Observations
Let $x=(x_1,\dots,x_r)$ be the vector of reservoir inflows produced by some station-splitting strategy. The set of all such vectors is convex, because every station can mix two strategies by using the corresponding convex combination of its split ratios.
For any weight vector $\lambda \ge 0$ with $\sum \lambda_i = 1$, maximizing $\lambda \cdot x$ is easy. If $H_i(\lambda)$ denotes the best weighted value obtainable from station $i$, then \[ H_i(\lambda)=\max_{\text{duct }d\text{ out of }i} \left(\sum_{j=1}^{r} \lambda_j \cdot \text{res}_{d,j} + \sum_{k} \text{frac}_{d,k} \cdot H_k(\lambda)\right). \]
The original objective is \[ \max_x \min_j x_j. \] Over a convex feasible set this is equal to \[ \min_{\lambda \ge 0,\ \sum \lambda_i=1} \max_x \lambda \cdot x. \] So we only need to minimize $H_1(\lambda)$ over the simplex.
Since $r \le 3$, the simplex has dimension at most $2$. The function $H_1$ is convex because it is the pointwise maximum of linear functions, so one-dimensional and nested golden-section searches are sufficient.
Algorithm
Store every duct of every station as: the fixed fractions going to later stations, and the fixed fractions going directly to the reservoirs.
For a fixed $\lambda$, evaluate $H_i(\lambda)$ by dynamic programming from station $s$ down to station $1$, since all edges go to larger indices.
If $r=1$, the answer is just $H_1(1)$.
If $r=2$, write $\lambda=(t,1-t)$ and minimize the convex function $H_1(t,1-t)$ on $[0,1]$ by golden-section search.
If $r=3$, write $\lambda=(x,y,1-x-y)$ with $x,y \ge 0$ and $x+y \le 1$. For a fixed $y$, minimize over $x \in [0,1-y]$ by golden search, then minimize the resulting convex function of $y$ by another golden search.
Correctness Proof
We prove that the algorithm returns the correct answer.
Lemma 1.
For a fixed weight vector $\lambda$, the dynamic program computes the maximum possible value of $\lambda \cdot x$ obtainable from each station.
Proof.
Consider a station $i$. Once the incoming flow reaches $i$, the only decision is how to split that flow among its outgoing ducts. Because the weighted objective is linear, sending all flow through the outgoing duct with the largest downstream weighted value is optimal. The formula used by the program is exactly that best value. Processing stations in reverse order is valid because every duct goes only to larger indices, so all needed subproblems are already known. □
Lemma 2.
The optimum minimum reservoir inflow equals \[ \min_{\lambda \ge 0,\ \sum \lambda_i=1} H_1(\lambda). \]
Proof.
Let $F$ be the convex set of all feasible reservoir vectors. For any feasible $x$ and any simplex weight vector $\lambda$, we have $\min_j x_j \le \lambda \cdot x$. Therefore \[ \max_{x \in F} \min_j x_j \le \min_{\lambda} \max_{x \in F} \lambda \cdot x. \] Conversely, if $z$ is the optimal max-min value, then the set $F$ intersects the box $[z,\infty)^r$ and does not intersect the interior of any strictly larger such box. A supporting hyperplane of $F$ at an optimal point gives a simplex weight vector $\lambda$ for which $\max_{x \in F}\lambda \cdot x = z$. Hence equality holds. □
Theorem.
The algorithm outputs the correct answer for every valid input.
Proof.
By Lemma 1, for every tested $\lambda$ the program evaluates $H_1(\lambda)$ correctly. By Lemma 2, the true answer is the minimum of this function over the simplex. Because $H_1$ is convex, its restriction to any line segment is also convex, hence unimodal. Therefore the golden-section searches used by the program converge to the global minimum in the one-dimensional and nested two-dimensional cases covered by $r \le 3$. Thus the printed value is the optimal minimum reservoir inflow. □
Complexity Analysis
Let $D$ be the number of ducts. One evaluation of $H_1(\lambda)$ costs $O(D \cdot r)$, which is $O(D)$ because $r \le 3$. The search performs only a fixed number of evaluations, so the total running time is $O(D)$ up to a constant factor, and the memory usage is $O(D)$.
Implementation Notes
The output is a percentage, so the program multiplies the final fraction by $100$.
Using about $80$--$200$ golden-search iterations is easily enough for the required $10^{-6}$ absolute error.
Code
Exact copied C++ implementation from solution.cpp.
#include <bits/stdc++.h>
using namespace std;
namespace {
struct Pipe {
vector<pair<int, double>> station_edges;
vector<double> reservoir_edges;
};
int s, r, d;
vector<vector<Pipe>> pipes;
double value_for_weights(const vector<double>& w) {
vector<double> best(s + 1, 0.0);
for (int u = s; u >= 1; --u) {
double cur = -1e100;
for (const Pipe& pipe : pipes[u]) {
double cand = 0.0;
for (int i = 0; i < r; ++i) {
cand += w[i] * pipe.reservoir_edges[i];
}
for (const auto& edge : pipe.station_edges) {
cand += edge.second * best[edge.first];
}
cur = max(cur, cand);
}
best[u] = cur;
}
return best[1];
}
double golden_min(const function<double(double)>& f, double lo, double hi, int iters) {
const double PHI = (sqrt(5.0) - 1.0) / 2.0;
double x1 = hi - PHI * (hi - lo);
double x2 = lo + PHI * (hi - lo);
double y1 = f(x1);
double y2 = f(x2);
for (int it = 0; it < iters; ++it) {
if (y1 > y2) {
lo = x1;
x1 = x2;
y1 = y2;
x2 = lo + PHI * (hi - lo);
y2 = f(x2);
} else {
hi = x2;
x2 = x1;
y2 = y1;
x1 = hi - PHI * (hi - lo);
y1 = f(x1);
}
}
return min(y1, y2);
}
} // namespace
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s >> r >> d;
pipes.assign(s + 1, {});
for (int i = 0; i < d; ++i) {
int at, cnt;
cin >> at >> cnt;
Pipe pipe;
pipe.reservoir_edges.assign(r, 0.0);
for (int j = 0; j < cnt; ++j) {
int to, p;
cin >> to >> p;
double frac = p / 100.0;
if (to <= s) {
pipe.station_edges.push_back({to, frac});
} else {
pipe.reservoir_edges[to - s - 1] += frac;
}
}
pipes[at].push_back(move(pipe));
}
cout << fixed << setprecision(10);
if (r == 1) {
cout << 100.0 * value_for_weights(vector<double>(1, 1.0)) << '\n';
return 0;
}
if (r == 2) {
auto f = [&](double x) {
vector<double> w = {x, 1.0 - x};
return value_for_weights(w);
};
cout << 100.0 * golden_min(f, 0.0, 1.0, 200) << '\n';
return 0;
}
auto inner = [&](double y) {
auto g = [&](double x) {
vector<double> w = {x, y, 1.0 - x - y};
return value_for_weights(w);
};
return golden_min(g, 0.0, max(0.0, 1.0 - y), 80);
};
cout << 100.0 * golden_min(inner, 0.0, 1.0, 80) << '\n';
return 0;
}
Source Files
Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.
\documentclass[11pt]{article}
\usepackage[margin=1in]{geometry}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{enumitem}
\title{ICPC World Finals 2025\\C. Bride of Pipe Stream}
\author{}
\date{}
\begin{document}
\maketitle
\section*{Problem Summary}
At every station we may split the incoming flow arbitrarily among the outgoing ducts, while each duct
then distributes its flow to lower stations and reservoirs by fixed percentages. The graph is acyclic
because every duct only goes to larger-numbered objects. We must maximize the minimum percentage of
the total source flow that reaches any reservoir.
\section*{Key Observations}
\begin{itemize}[leftmargin=*]
\item Let $x=(x_1,\dots,x_r)$ be the vector of reservoir inflows produced by some station-splitting
strategy. The set of all such vectors is convex, because every station can mix two strategies by using
the corresponding convex combination of its split ratios.
\item For any weight vector $\lambda \ge 0$ with $\sum \lambda_i = 1$, maximizing
$\lambda \cdot x$ is easy. If $H_i(\lambda)$ denotes the best weighted value obtainable from station $i$,
then
\[
H_i(\lambda)=\max_{\text{duct }d\text{ out of }i}
\left(\sum_{j=1}^{r} \lambda_j \cdot \text{res}_{d,j}
+ \sum_{k} \text{frac}_{d,k} \cdot H_k(\lambda)\right).
\]
\item The original objective is
\[
\max_x \min_j x_j.
\]
Over a convex feasible set this is equal to
\[
\min_{\lambda \ge 0,\ \sum \lambda_i=1} \max_x \lambda \cdot x.
\]
So we only need to minimize $H_1(\lambda)$ over the simplex.
\item Since $r \le 3$, the simplex has dimension at most $2$. The function $H_1$ is convex because it
is the pointwise maximum of linear functions, so one-dimensional and nested golden-section searches
are sufficient.
\end{itemize}
\section*{Algorithm}
\begin{enumerate}[leftmargin=*]
\item Store every duct of every station as:
the fixed fractions going to later stations, and the fixed fractions going directly to the reservoirs.
\item For a fixed $\lambda$, evaluate $H_i(\lambda)$ by dynamic programming from station $s$ down to
station $1$, since all edges go to larger indices.
\item If $r=1$, the answer is just $H_1(1)$.
\item If $r=2$, write $\lambda=(t,1-t)$ and minimize the convex function $H_1(t,1-t)$ on $[0,1]$ by
golden-section search.
\item If $r=3$, write $\lambda=(x,y,1-x-y)$ with $x,y \ge 0$ and $x+y \le 1$.
For a fixed $y$, minimize over $x \in [0,1-y]$ by golden search, then minimize the resulting convex
function of $y$ by another golden search.
\end{enumerate}
\section*{Correctness Proof}
We prove that the algorithm returns the correct answer.
\paragraph{Lemma 1.}
For a fixed weight vector $\lambda$, the dynamic program computes the maximum possible value of
$\lambda \cdot x$ obtainable from each station.
\paragraph{Proof.}
Consider a station $i$. Once the incoming flow reaches $i$, the only decision is how to split that flow
among its outgoing ducts. Because the weighted objective is linear, sending all flow through the outgoing
duct with the largest downstream weighted value is optimal. The formula used by the program is exactly
that best value. Processing stations in reverse order is valid because every duct goes only to larger
indices, so all needed subproblems are already known. \qed
\paragraph{Lemma 2.}
The optimum minimum reservoir inflow equals
\[
\min_{\lambda \ge 0,\ \sum \lambda_i=1} H_1(\lambda).
\]
\paragraph{Proof.}
Let $F$ be the convex set of all feasible reservoir vectors. For any feasible $x$ and any simplex weight
vector $\lambda$, we have $\min_j x_j \le \lambda \cdot x$. Therefore
\[
\max_{x \in F} \min_j x_j \le \min_{\lambda} \max_{x \in F} \lambda \cdot x.
\]
Conversely, if $z$ is the optimal max-min value, then the set $F$ intersects the box
$[z,\infty)^r$ and does not intersect the interior of any strictly larger such box. A supporting
hyperplane of $F$ at an optimal point gives a simplex weight vector $\lambda$ for which
$\max_{x \in F}\lambda \cdot x = z$. Hence equality holds. \qed
\paragraph{Theorem.}
The algorithm outputs the correct answer for every valid input.
\paragraph{Proof.}
By Lemma 1, for every tested $\lambda$ the program evaluates $H_1(\lambda)$ correctly. By Lemma 2,
the true answer is the minimum of this function over the simplex. Because $H_1$ is convex, its
restriction to any line segment is also convex, hence unimodal. Therefore the golden-section searches
used by the program converge to the global minimum in the one-dimensional and nested two-dimensional
cases covered by $r \le 3$. Thus the printed value is the optimal minimum reservoir inflow. \qed
\section*{Complexity Analysis}
Let $D$ be the number of ducts. One evaluation of $H_1(\lambda)$ costs $O(D \cdot r)$, which is
$O(D)$ because $r \le 3$. The search performs only a fixed number of evaluations, so the total running
time is $O(D)$ up to a constant factor, and the memory usage is $O(D)$.
\section*{Implementation Notes}
\begin{itemize}[leftmargin=*]
\item The output is a percentage, so the program multiplies the final fraction by $100$.
\item Using about $80$--$200$ golden-search iterations is easily enough for the required $10^{-6}$
absolute error.
\end{itemize}
\end{document}
#include <bits/stdc++.h>
using namespace std;
namespace {
struct Pipe {
vector<pair<int, double>> station_edges;
vector<double> reservoir_edges;
};
int s, r, d;
vector<vector<Pipe>> pipes;
double value_for_weights(const vector<double>& w) {
vector<double> best(s + 1, 0.0);
for (int u = s; u >= 1; --u) {
double cur = -1e100;
for (const Pipe& pipe : pipes[u]) {
double cand = 0.0;
for (int i = 0; i < r; ++i) {
cand += w[i] * pipe.reservoir_edges[i];
}
for (const auto& edge : pipe.station_edges) {
cand += edge.second * best[edge.first];
}
cur = max(cur, cand);
}
best[u] = cur;
}
return best[1];
}
double golden_min(const function<double(double)>& f, double lo, double hi, int iters) {
const double PHI = (sqrt(5.0) - 1.0) / 2.0;
double x1 = hi - PHI * (hi - lo);
double x2 = lo + PHI * (hi - lo);
double y1 = f(x1);
double y2 = f(x2);
for (int it = 0; it < iters; ++it) {
if (y1 > y2) {
lo = x1;
x1 = x2;
y1 = y2;
x2 = lo + PHI * (hi - lo);
y2 = f(x2);
} else {
hi = x2;
x2 = x1;
y2 = y1;
x1 = hi - PHI * (hi - lo);
y1 = f(x1);
}
}
return min(y1, y2);
}
} // namespace
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s >> r >> d;
pipes.assign(s + 1, {});
for (int i = 0; i < d; ++i) {
int at, cnt;
cin >> at >> cnt;
Pipe pipe;
pipe.reservoir_edges.assign(r, 0.0);
for (int j = 0; j < cnt; ++j) {
int to, p;
cin >> to >> p;
double frac = p / 100.0;
if (to <= s) {
pipe.station_edges.push_back({to, frac});
} else {
pipe.reservoir_edges[to - s - 1] += frac;
}
}
pipes[at].push_back(move(pipe));
}
cout << fixed << setprecision(10);
if (r == 1) {
cout << 100.0 * value_for_weights(vector<double>(1, 1.0)) << '\n';
return 0;
}
if (r == 2) {
auto f = [&](double x) {
vector<double> w = {x, 1.0 - x};
return value_for_weights(w);
};
cout << 100.0 * golden_min(f, 0.0, 1.0, 200) << '\n';
return 0;
}
auto inner = [&](double y) {
auto g = [&](double x) {
vector<double> w = {x, y, 1.0 - x - y};
return value_for_weights(w);
};
return golden_min(g, 0.0, max(0.0, 1.0 - y), 80);
};
cout << 100.0 * golden_min(inner, 0.0, 1.0, 80) << '\n';
return 0;
}