All IOI entries
Competitive Programming

IOI 2016 - Railroad

A roller coaster has n sections. Section i has a starting speed s_i and an ending speed t_i. To connect section i to section j: If t_i s_j: free (braking is free). If t_i < s_j: costs s_j - t_i (need to accelerate). F...

Source sync Apr 19, 2026
Track IOI
Year 2016
Files TeX and C++
Folder competitive_programming/ioi/2016/railroad
IOI2016TeXC++

Source-first archive entry

This page is built from the copied files in competitive_programming/ioi/2016/railroad. Edit competitive_programming/ioi/2016/railroad/solution.tex to update the written solution and competitive_programming/ioi/2016/railroad/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

Rendered from the "Problem Summary" section inside solution.tex because this entry has no separate statement file.

A roller coaster has $n$ sections. Section $i$ has a starting speed $s_i$ and an ending speed $t_i$. To connect section $i$ to section $j$:

  • If $t_i \ge s_j$: free (braking is free).

  • If $t_i < s_j$: costs $s_j - t_i$ (need to accelerate).

  • Find the minimum cost to arrange all sections into a single circuit (Eulerian circuit), visiting each section exactly once.

Editorial

Rendered from the copied solution.tex file. The original TeX source remains available below.

Solution Approach

Model as a graph problem. Create a directed graph where speeds are nodes. For each section $i$, add an edge from $s_i$ to $t_i$ (this edge is ``free'' -- it represents riding section $i$). We need to add additional edges to make the graph have an Eulerian circuit.

Step 1: Degree balancing. For an Eulerian circuit, each node must have equal in-degree and out-degree. For each node $v$:

  • If $\text{in}(v) > \text{out}(v)$: need $\text{in}(v) - \text{out}(v)$ outgoing edges from $v$.

  • If $\text{out}(v) > \text{in}(v)$: need $\text{out}(v) - \text{in}(v)$ incoming edges to $v$.

  • Going from speed $a$ to speed $b$:

  • $a \ge b$: cost 0 (braking).

  • $a < b$: cost $b - a$.

  • So the cost of an added edge from $a$ to $b$ is $\max(0, b - a)$.

    Step 2: Match excess outgoing with excess incoming. Sort all speeds. Excess outgoing at speed $a$ should go to excess incoming at the nearest speed $b \ge a$ (for free if $b \ge a$, but going down costs nothing too). Actually, going from higher to lower is free.

    Use a sweep from right to left on sorted speeds. Accumulate excess degrees. The cost is incurred when we need to ``go up'' in speed.

    Step 3: Connectivity. After degree balancing, the graph might not be connected. We need to add edges to connect components. Use a Union-Find on the speed nodes that have edges, and add minimum-cost edges between components.

    The cost to connect two adjacent speed values $v_1 < v_2$ is $v_2 - v_1$ (or 0 if going down, but we need bidirectional connectivity, so we need at least one up-edge).

C++ Solution

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

struct DSU {
    vector<int> p, rank_;
    void init(int n) {
        p.resize(n); rank_.resize(n, 0);
        iota(p.begin(), p.end(), 0);
    }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    bool unite(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return false;
        if (rank_[a] < rank_[b]) swap(a, b);
        p[b] = a;
        if (rank_[a] == rank_[b]) rank_[a]++;
        return true;
    }
};

long long plan_roller_coaster(int n, int s[], int t[]) {
    // Coordinate compress all speeds
    vector<int> vals;
    for (int i = 0; i < n; i++) {
        vals.push_back(s[i]);
        vals.push_back(t[i]);
    }
    // Add a "zero" node for the circuit starting point
    vals.push_back(0);
    sort(vals.begin(), vals.end());
    vals.erase(unique(vals.begin(), vals.end()), vals.end());
    int V = vals.size();

    auto compress = [&](int x) {
        return lower_bound(vals.begin(), vals.end(), x) - vals.begin();
    };

    // Compute degree balance at each speed node
    // Each section i: edge from s[i] to t[i]
    // in-degree of t[i] += 1, out-degree of s[i] += 1
    vector<int> balance(V, 0); // balance = out - in
    DSU dsu;
    dsu.init(V);

    for (int i = 0; i < n; i++) {
        int u = compress(s[i]), v = compress(t[i]);
        balance[u]++; // out-degree
        balance[v]--; // in-degree
        dsu.unite(u, v);
    }

    // The circuit needs to return to start.
    // Add a "base" node at speed 0. The circuit starts and ends here.
    // Actually, for the Eulerian circuit, we need the total system to be balanced.
    // Add one edge from node 0 to some node, and back.
    // The circuit: start at speed 0, ride sections, return to speed 0.
    // So we need an edge from 0 -> s[first] and t[last] -> 0.
    // For Eulerian: just ensure balance at each node and connectivity.

    // For the circuit: add one outgoing from node 0 and one incoming to node 0
    // But this is implicit in the circuit structure. Actually the problem says
    // arrange sections into a circuit, so we need an Eulerian circuit on the
    // section edges plus any added "transition" edges.

    // The trick: we need to add edges to balance degrees and connect components.
    // Sweep from highest speed to lowest:
    // - Accumulate excess = sum of balance[v] for v >= current
    // - If excess < 0 at some point: we need |excess| edges going "up" from
    //   current speed to the next higher speed, costing (next - current) each.
    // - Also need connectivity.

    // First, connect components for connectivity
    // All nodes with edges are in the DSU. Also include node 0.
    // Connect all components: sort by value, connect adjacent with cost diff.

    // Which nodes are "active"? Those with edges or non-zero balance.
    vector<bool> active(V, false);
    active[compress(0)] = true; // base node
    for (int i = 0; i < n; i++) {
        active[compress(s[i])] = true;
        active[compress(t[i])] = true;
    }

    // For connectivity: connect adjacent active nodes
    // Cost of connecting nodes at speeds a < b: b - a (need an up-edge)
    // But actually, connecting down is free.
    // For Eulerian circuit on an undirected-like structure, we just need connectivity.
    // The cost to connect two components at speeds a and b (a < b) is b - a.

    // Collect active node indices sorted by value
    vector<int> activeNodes;
    for (int i = 0; i < V; i++) {
        if (active[i]) activeNodes.push_back(i);
    }

    ll cost = 0;

    // Connect components using edges between adjacent active nodes
    for (int i = 0; i + 1 < (int)activeNodes.size(); i++) {
        int u = activeNodes[i], v = activeNodes[i+1];
        if (dsu.unite(u, v)) {
            // Must add an edge from u to v (cost = vals[v] - vals[u])
            // and an edge from v to u (cost = 0, since going down is free)
            cost += vals[v] - vals[u];
            balance[u]++; // outgoing from u
            balance[v]--; // incoming to v
            // Also add return edge (free)
            balance[v]++; // outgoing from v
            balance[u]--; // incoming to u
        }
    }

    // Now balance degrees using sweep from right to left
    // At each active node, accumulate excess. If excess > 0, we have more
    // outgoing than incoming, need to send excess to the right (but that costs
    // money going up). If excess < 0, we have more incoming, need to send
    // excess to the left (free going down).
    // Sweep from right to left, carry excess.
    // Cost is incurred when excess > 0 at a transition going right.

    // Actually, sweep from left to right:
    // carry = running sum of balance
    // At each step, if carry > 0: we need 'carry' transitions going left (free)
    // If carry < 0: we need |carry| transitions going right (cost per unit = gap)
    // Wait, this needs more care.

    // Correct approach: sweep left to right on active nodes.
    // carry tracks the net flow that needs to go right.
    // carry = sum of balance[0..i]. If carry > 0, we have carry units of flow
    // going right from node i to node i+1, cost = carry * (vals[i+1] - vals[i]).
    // If carry < 0, the flow goes left (free).

    ll carry = 0;
    for (int i = 0; i + 1 < (int)activeNodes.size(); i++) {
        carry += balance[activeNodes[i]];
        if (carry > 0) {
            cost += carry * (ll)(vals[activeNodes[i+1]] - vals[activeNodes[i]]);
        }
    }

    return cost;
}

int main() {
    int n;
    scanf("%d", &n);
    int s[n], t[n];
    for (int i = 0; i < n; i++) scanf("%d %d", &s[i], &t[i]);
    printf("%lld\n", plan_roller_coaster(n, s, t));
    return 0;
}

Complexity Analysis

  • Time: $O(n \log n)$ for sorting and coordinate compression, $O(n \alpha(n))$ for DSU operations.

  • Space: $O(n)$.

Code

Exact copied C++ implementation from solution.cpp.

C++ competitive_programming/ioi/2016/railroad/solution.cpp

Exact copied implementation source.

Raw file
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

struct DSU {
    vector<int> p, rank_;
    void init(int n) {
        p.resize(n); rank_.resize(n, 0);
        iota(p.begin(), p.end(), 0);
    }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    bool unite(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return false;
        if (rank_[a] < rank_[b]) swap(a, b);
        p[b] = a;
        if (rank_[a] == rank_[b]) rank_[a]++;
        return true;
    }
};

long long plan_roller_coaster(int n, int s[], int t[]) {
    // Coordinate compress all speeds
    vector<int> vals;
    for (int i = 0; i < n; i++) {
        vals.push_back(s[i]);
        vals.push_back(t[i]);
    }
    // Add a "zero" node for the circuit starting point
    vals.push_back(0);
    sort(vals.begin(), vals.end());
    vals.erase(unique(vals.begin(), vals.end()), vals.end());
    int V = vals.size();

    auto compress = [&](int x) {
        return lower_bound(vals.begin(), vals.end(), x) - vals.begin();
    };

    // Compute degree balance at each speed node
    // Each section i: edge from s[i] to t[i]
    // in-degree of t[i] += 1, out-degree of s[i] += 1
    vector<int> balance(V, 0); // balance = out - in
    DSU dsu;
    dsu.init(V);

    for (int i = 0; i < n; i++) {
        int u = compress(s[i]), v = compress(t[i]);
        balance[u]++; // out-degree
        balance[v]--; // in-degree
        dsu.unite(u, v);
    }

    // The circuit needs to return to start.
    // Add a "base" node at speed 0. The circuit starts and ends here.
    // Actually, for the Eulerian circuit, we need the total system to be balanced.
    // Add one edge from node 0 to some node, and back.
    // The circuit: start at speed 0, ride sections, return to speed 0.
    // So we need an edge from 0 -> s[first] and t[last] -> 0.
    // For Eulerian: just ensure balance at each node and connectivity.

    // For the circuit: add one outgoing from node 0 and one incoming to node 0
    // But this is implicit in the circuit structure. Actually the problem says
    // arrange sections into a circuit, so we need an Eulerian circuit on the
    // section edges plus any added "transition" edges.

    // The trick: we need to add edges to balance degrees and connect components.
    // Sweep from highest speed to lowest:
    // - Accumulate excess = sum of balance[v] for v >= current
    // - If excess < 0 at some point: we need |excess| edges going "up" from
    //   current speed to the next higher speed, costing (next - current) each.
    // - Also need connectivity.

    // First, connect components for connectivity
    // All nodes with edges are in the DSU. Also include node 0.
    // Connect all components: sort by value, connect adjacent with cost diff.

    // Which nodes are "active"? Those with edges or non-zero balance.
    vector<bool> active(V, false);
    active[compress(0)] = true; // base node
    for (int i = 0; i < n; i++) {
        active[compress(s[i])] = true;
        active[compress(t[i])] = true;
    }

    // For connectivity: connect adjacent active nodes
    // Cost of connecting nodes at speeds a < b: b - a (need an up-edge)
    // But actually, connecting down is free.
    // For Eulerian circuit on an undirected-like structure, we just need connectivity.
    // The cost to connect two components at speeds a and b (a < b) is b - a.

    // Collect active node indices sorted by value
    vector<int> activeNodes;
    for (int i = 0; i < V; i++) {
        if (active[i]) activeNodes.push_back(i);
    }

    ll cost = 0;

    // Connect components using edges between adjacent active nodes
    for (int i = 0; i + 1 < (int)activeNodes.size(); i++) {
        int u = activeNodes[i], v = activeNodes[i+1];
        if (dsu.unite(u, v)) {
            // Must add an edge from u to v (cost = vals[v] - vals[u])
            // and an edge from v to u (cost = 0, since going down is free)
            cost += vals[v] - vals[u];
            balance[u]++; // outgoing from u
            balance[v]--; // incoming to v
            // Also add return edge (free)
            balance[v]++; // outgoing from v
            balance[u]--; // incoming to u
        }
    }

    // Now balance degrees using sweep from right to left
    // At each active node, accumulate excess. If excess > 0, we have more
    // outgoing than incoming, need to send excess to the right (but that costs
    // money going up). If excess < 0, we have more incoming, need to send
    // excess to the left (free going down).
    // Sweep from right to left, carry excess.
    // Cost is incurred when excess > 0 at a transition going right.

    // Actually, sweep from left to right:
    // carry = running sum of balance
    // At each step, if carry > 0: we need 'carry' transitions going left (free)
    // If carry < 0: we need |carry| transitions going right (cost per unit = gap)
    // Wait, this needs more care.

    // Correct approach: sweep left to right on active nodes.
    // carry tracks the net flow that needs to go right.
    // carry = sum of balance[0..i]. If carry > 0, we have carry units of flow
    // going right from node i to node i+1, cost = carry * (vals[i+1] - vals[i]).
    // If carry < 0, the flow goes left (free).

    ll carry = 0;
    for (int i = 0; i + 1 < (int)activeNodes.size(); i++) {
        carry += balance[activeNodes[i]];
        if (carry > 0) {
            cost += carry * (ll)(vals[activeNodes[i+1]] - vals[activeNodes[i]]);
        }
    }

    return cost;
}

int main() {
    int n;
    scanf("%d", &n);
    int s[n], t[n];
    for (int i = 0; i < n; i++) scanf("%d %d", &s[i], &t[i]);
    printf("%lld\n", plan_roller_coaster(n, s, t));
    return 0;
}

Source Files

Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.

TeX write-up competitive_programming/ioi/2016/railroad/solution.tex

Exact copied write-up source.

Raw file
\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[margin=1in]{geometry}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{hyperref}

\lstset{
  language=C++,
  basicstyle=\ttfamily\small,
  keywordstyle=\color{blue}\bfseries,
  commentstyle=\color{gray},
  stringstyle=\color{red},
  numbers=left,
  numberstyle=\tiny\color{gray},
  breaklines=true,
  frame=single,
  tabsize=4,
  showstringspaces=false
}

\title{IOI 2016 -- Railroad}
\author{Solution}
\date{}

\begin{document}
\maketitle

\section{Problem Summary}

A roller coaster has $n$ sections. Section $i$ has a starting speed $s_i$ and an ending speed $t_i$. To connect section $i$ to section $j$:
\begin{itemize}
  \item If $t_i \ge s_j$: free (braking is free).
  \item If $t_i < s_j$: costs $s_j - t_i$ (need to accelerate).
\end{itemize}

Find the minimum cost to arrange all sections into a single circuit (Eulerian circuit), visiting each section exactly once.

\section{Solution Approach}

\textbf{Model as a graph problem}. Create a directed graph where speeds are nodes. For each section $i$, add an edge from $s_i$ to $t_i$ (this edge is ``free'' -- it represents riding section $i$). We need to add additional edges to make the graph have an Eulerian circuit.

\textbf{Step 1: Degree balancing.} For an Eulerian circuit, each node must have equal in-degree and out-degree. For each node $v$:
\begin{itemize}
  \item If $\text{in}(v) > \text{out}(v)$: need $\text{in}(v) - \text{out}(v)$ outgoing edges from $v$.
  \item If $\text{out}(v) > \text{in}(v)$: need $\text{out}(v) - \text{in}(v)$ incoming edges to $v$.
\end{itemize}

Going from speed $a$ to speed $b$:
\begin{itemize}
  \item $a \ge b$: cost 0 (braking).
  \item $a < b$: cost $b - a$.
\end{itemize}

So the cost of an added edge from $a$ to $b$ is $\max(0, b - a)$.

\textbf{Step 2: Match excess outgoing with excess incoming.} Sort all speeds. Excess outgoing at speed $a$ should go to excess incoming at the nearest speed $b \ge a$ (for free if $b \ge a$, but going down costs nothing too). Actually, going from higher to lower is free.

Use a sweep from right to left on sorted speeds. Accumulate excess degrees. The cost is incurred when we need to ``go up'' in speed.

\textbf{Step 3: Connectivity.} After degree balancing, the graph might not be connected. We need to add edges to connect components. Use a Union-Find on the speed nodes that have edges, and add minimum-cost edges between components.

The cost to connect two adjacent speed values $v_1 < v_2$ is $v_2 - v_1$ (or 0 if going down, but we need bidirectional connectivity, so we need at least one up-edge).

\section{C++ Solution}

\begin{lstlisting}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

struct DSU {
    vector<int> p, rank_;
    void init(int n) {
        p.resize(n); rank_.resize(n, 0);
        iota(p.begin(), p.end(), 0);
    }
    int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
    bool unite(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return false;
        if (rank_[a] < rank_[b]) swap(a, b);
        p[b] = a;
        if (rank_[a] == rank_[b]) rank_[a]++;
        return true;
    }
};

long long plan_roller_coaster(int n, int s[], int t[]) {
    // Coordinate compress all speeds
    vector<int> vals;
    for (int i = 0; i < n; i++) {
        vals.push_back(s[i]);
        vals.push_back(t[i]);
    }
    // Add a "zero" node for the circuit starting point
    vals.push_back(0);
    sort(vals.begin(), vals.end());
    vals.erase(unique(vals.begin(), vals.end()), vals.end());
    int V = vals.size();

    auto compress = [&](int x) {
        return lower_bound(vals.begin(), vals.end(), x) - vals.begin();
    };

    // Compute degree balance at each speed node
    // Each section i: edge from s[i] to t[i]
    // in-degree of t[i] += 1, out-degree of s[i] += 1
    vector<int> balance(V, 0); // balance = out - in
    DSU dsu;
    dsu.init(V);

    for (int i = 0; i < n; i++) {
        int u = compress(s[i]), v = compress(t[i]);
        balance[u]++; // out-degree
        balance[v]--; // in-degree
        dsu.unite(u, v);
    }

    // The circuit needs to return to start.
    // Add a "base" node at speed 0. The circuit starts and ends here.
    // Actually, for the Eulerian circuit, we need the total system to be balanced.
    // Add one edge from node 0 to some node, and back.
    // The circuit: start at speed 0, ride sections, return to speed 0.
    // So we need an edge from 0 -> s[first] and t[last] -> 0.
    // For Eulerian: just ensure balance at each node and connectivity.

    // For the circuit: add one outgoing from node 0 and one incoming to node 0
    // But this is implicit in the circuit structure. Actually the problem says
    // arrange sections into a circuit, so we need an Eulerian circuit on the
    // section edges plus any added "transition" edges.

    // The trick: we need to add edges to balance degrees and connect components.
    // Sweep from highest speed to lowest:
    // - Accumulate excess = sum of balance[v] for v >= current
    // - If excess < 0 at some point: we need |excess| edges going "up" from
    //   current speed to the next higher speed, costing (next - current) each.
    // - Also need connectivity.

    // First, connect components for connectivity
    // All nodes with edges are in the DSU. Also include node 0.
    // Connect all components: sort by value, connect adjacent with cost diff.

    // Which nodes are "active"? Those with edges or non-zero balance.
    vector<bool> active(V, false);
    active[compress(0)] = true; // base node
    for (int i = 0; i < n; i++) {
        active[compress(s[i])] = true;
        active[compress(t[i])] = true;
    }

    // For connectivity: connect adjacent active nodes
    // Cost of connecting nodes at speeds a < b: b - a (need an up-edge)
    // But actually, connecting down is free.
    // For Eulerian circuit on an undirected-like structure, we just need connectivity.
    // The cost to connect two components at speeds a and b (a < b) is b - a.

    // Collect active node indices sorted by value
    vector<int> activeNodes;
    for (int i = 0; i < V; i++) {
        if (active[i]) activeNodes.push_back(i);
    }

    ll cost = 0;

    // Connect components using edges between adjacent active nodes
    for (int i = 0; i + 1 < (int)activeNodes.size(); i++) {
        int u = activeNodes[i], v = activeNodes[i+1];
        if (dsu.unite(u, v)) {
            // Must add an edge from u to v (cost = vals[v] - vals[u])
            // and an edge from v to u (cost = 0, since going down is free)
            cost += vals[v] - vals[u];
            balance[u]++; // outgoing from u
            balance[v]--; // incoming to v
            // Also add return edge (free)
            balance[v]++; // outgoing from v
            balance[u]--; // incoming to u
        }
    }

    // Now balance degrees using sweep from right to left
    // At each active node, accumulate excess. If excess > 0, we have more
    // outgoing than incoming, need to send excess to the right (but that costs
    // money going up). If excess < 0, we have more incoming, need to send
    // excess to the left (free going down).
    // Sweep from right to left, carry excess.
    // Cost is incurred when excess > 0 at a transition going right.

    // Actually, sweep from left to right:
    // carry = running sum of balance
    // At each step, if carry > 0: we need 'carry' transitions going left (free)
    // If carry < 0: we need |carry| transitions going right (cost per unit = gap)
    // Wait, this needs more care.

    // Correct approach: sweep left to right on active nodes.
    // carry tracks the net flow that needs to go right.
    // carry = sum of balance[0..i]. If carry > 0, we have carry units of flow
    // going right from node i to node i+1, cost = carry * (vals[i+1] - vals[i]).
    // If carry < 0, the flow goes left (free).

    ll carry = 0;
    for (int i = 0; i + 1 < (int)activeNodes.size(); i++) {
        carry += balance[activeNodes[i]];
        if (carry > 0) {
            cost += carry * (ll)(vals[activeNodes[i+1]] - vals[activeNodes[i]]);
        }
    }

    return cost;
}

int main() {
    int n;
    scanf("%d", &n);
    int s[n], t[n];
    for (int i = 0; i < n; i++) scanf("%d %d", &s[i], &t[i]);
    printf("%lld\n", plan_roller_coaster(n, s, t));
    return 0;
}
\end{lstlisting}

\section{Complexity Analysis}

\begin{itemize}
  \item \textbf{Time}: $O(n \log n)$ for sorting and coordinate compression, $O(n \alpha(n))$ for DSU operations.
  \item \textbf{Space}: $O(n)$.
\end{itemize}

\end{document}