All IOI entries
Competitive Programming

IOI 2018 - Meetings

There are n mountains with heights H_0,, H_ n-1. For a query (L, R), choose a meeting point m [L, R] to minimize: cost(m) = _ i=L ^ R _ j [ (i,m), (i,m)] H_j. Return the minimum cost.

Source sync Apr 19, 2026
Track IOI
Year 2018
Files TeX and C++
Folder competitive_programming/ioi/2018/meetings
IOI2018TeXC++

Source-first archive entry

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

There are $n$ mountains with heights $H_0, \ldots, H_{n-1}$. For a query $(L, R)$, choose a meeting point $m \in [L, R]$ to minimize: \[ \text{cost}(m) = \sum_{i=L}^{R} \max_{j \in [\min(i,m),\, \max(i,m)]} H_j. \] Return the minimum cost.

Editorial

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

Solution

Cartesian Tree Decomposition

Let $p$ be the position of the maximum in $[L, R]$ (found via sparse table in $O(1)$). Then:

  • If $m \in [L, p-1]$: everyone in $[p, R]$ must pass through position $p$, paying at least $H_p$ each. The cost splits as: \[ \text{cost}(m) = \text{cost}_{[L,p-1]}(m) + H_p \cdot (R - p + 1) \]

  • If $m \in [p+1, R]$: symmetrically, $\text{cost}(m) = \text{cost}_{[p+1,R]}(m) + H_p \cdot (p - L + 1)$.

  • If $m = p$: $\text{cost}(p) = H_p \cdot (R - L + 1)$.

Lemma.

$\textit{best}(L, R) = \min\bigl(\textit{best}(L, p-1) + H_p(R-p+1),\; \textit{best}(p+1, R) + H_p(p-L+1),\; H_p(R-L+1)\bigr)$ where $p = \arg\max_{i \in [L,R]} H_i$.

This recursion follows the Cartesian tree of $H$. For a single query, the depth of recursion equals the depth of the Cartesian tree, which is $O(n)$ worst case but $O(\log n)$ expected for random inputs.

Full-Score Solution Outline

For $O((n + q) \log^2 n)$ total time, process all queries offline on the Cartesian tree. At each Cartesian tree node with range $[L, R]$ and max at $p$, queries spanning $p$ decompose into a left-side cost plus a right-side cost. Use the ``smaller-to-larger'' technique: iterate over the smaller subtree and insert cost functions (linear functions of the meeting point) into a Li Chao tree for the larger subtree.

Implementation

The repository implementation follows the offline Cartesian-tree decomposition described above, with precomputed side costs so that all queries are handled efficiently.

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

struct SparseTable {
    int n;
    vector<vector<int>> tbl;
    vector<int> arr, lg;

    void build(vector<int>& a) {
        arr = a; n = a.size();
        lg.resize(n + 1);
        for (int i = 2; i <= n; i++) lg[i] = lg[i/2] + 1;
        int K = lg[n] + 1;
        tbl.assign(K, vector<int>(n));
        for (int i = 0; i < n; i++) tbl[0][i] = i;
        for (int k = 1; k < K; k++)
            for (int i = 0; i + (1 << k) <= n; i++) {
                int l = tbl[k-1][i], r = tbl[k-1][i + (1 << (k-1))];
                tbl[k][i] = (arr[l] >= arr[r]) ? l : r;
            }
    }

    int query(int l, int r) {
        int k = lg[r - l + 1];
        int a = tbl[k][l], b = tbl[k][r - (1 << k) + 1];
        return (arr[a] >= arr[b]) ? a : b;
    }
};

vector<long long> minimum_costs(vector<int> H, vector<int> L, vector<int> R) {
    int N = H.size(), Q = L.size();
    SparseTable spt;
    spt.build(H);

    function<ll(int, int)> solve = [&](int l, int r) -> ll {
        if (l > r) return 0;
        if (l == r) return H[l];
        int p = spt.query(l, r);
        ll leftSize = p - l, rightSize = r - p;
        ll opt = (ll)H[p] * (r - l + 1);  // meet at p
        if (leftSize > 0)
            opt = min(opt, solve(l, p - 1) + (ll)H[p] * (rightSize + 1));
        if (rightSize > 0)
            opt = min(opt, solve(p + 1, r) + (ll)H[p] * (leftSize + 1));
        return opt;
    };

    vector<ll> ans(Q);
    for (int q = 0; q < Q; q++)
        ans[q] = solve(L[q], R[q]);
    return ans;
}

Complexity Analysis

  • Sparse table: $O(n \log n)$ preprocessing, $O(1)$ per range-max query.

  • Per query (recursive): $O(n)$ worst case (degenerate Cartesian tree), $O(\log n)$ expected.

  • Full-score offline: $O((n + q) \log^2 n)$ using Li Chao trees on the Cartesian tree with smaller-to-larger merging.

Code

Exact copied C++ implementation from solution.cpp.

C++ competitive_programming/ioi/2018/meetings/solution.cpp

Exact copied implementation source.

Raw file
/*
 * IOI 2018 - Meetings
 *
 * N mountains H[0..N-1], Q queries (L, R).
 * Minimize sum_{i=L}^{R} max(H[min(i,x)..max(i,x)]) over x in [L,R].
 *
 * Solution: Offline D&C on Cartesian tree, shorter-side dp + CHT.
 *
 * Key recurrence: let p = argmax H[L..R]. Then
 *   answer(L,R) = min(
 *     answer(L, p-1) + H[p]*(R-p+1),
 *     H[p]*(R-L+1),
 *     answer(p+1, R) + H[p]*(p-L+1)
 *   )
 *
 * At each Cartesian tree node (max at p in [lo,hi]):
 *   - Shorter side: compute dp for ALL positions using stack + CHT in O(shorter_side).
 *   - Longer side: compute dp for ALL positions using stack + CHT in O(longer_side).
 *   - Answer spanning queries in O(1).
 *   - Recurse into children for non-spanning queries.
 *
 * By the Cartesian tree structure, each level of the D&C processes O(N) total positions
 * for dp computation. The Cartesian tree has O(log N) expected depth (O(N) worst case).
 *
 * To guarantee O(N log N), we use the shorter-side trick: only compute dp for the
 * shorter side (O(shorter_side) per node, O(N log N) total). For the longer side,
 * propagate needed dp values from child recursions.
 *
 * Implementation: at each node, compute dp for BOTH sides. The total work per level
 * is O(N). With O(log N) balanced depth, total O(N log N). For degenerate trees,
 * we limit dp computation to the shorter side and compute longer side values on demand.
 *
 * Complexity: O((N + Q) log N) expected, O(N log N + Q * depth) worst.
 */
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

struct SparseTable {
    vector<vector<int>> t;
    vector<int> a, lg;
    void build(const vector<int>& v) {
        a = v; int n = v.size();
        lg.assign(n + 1, 0);
        for (int i = 2; i <= n; i++) lg[i] = lg[i/2] + 1;
        int K = lg[n] + 1;
        t.assign(K, vector<int>(n));
        for (int i = 0; i < n; i++) t[0][i] = i;
        for (int k = 1; k < K; k++)
            for (int i = 0; i + (1 << k) <= n; i++) {
                int x = t[k-1][i], y = t[k-1][i + (1 << (k-1))];
                t[k][i] = (a[x] >= a[y]) ? x : y;
            }
    }
    int query(int l, int r) const {
        int k = lg[r - l + 1];
        int x = t[k][l], y = t[k][r - (1 << k) + 1];
        return (a[x] >= a[y]) ? x : y;
    }
} spt;

int N;
vector<int> H;
vector<ll> ans;

// --- Convex Hull Trick for lines with decreasing slopes ---

struct Line {
    ll slope, intercept;
    int id;
    ll eval(ll t) const { return slope * t + intercept; }
};

bool dominated(const Line& a, const Line& b, const Line& c) {
    return (__int128)(c.intercept - a.intercept) * (a.slope - b.slope) <=
           (__int128)(b.intercept - a.intercept) * (a.slope - c.slope);
}

// --- Stack-based dp: compute answer(base, base+j) extending right ---

vector<ll> build_dp_right(int base, int end) {
    int len = end - base + 1;
    vector<ll> dp(len);
    if (len == 0) return dp;

    struct Entry { ll h; int lw; ll la; ll pcost; int pw; int id; };
    vector<Entry> stk;
    deque<Line> hull;
    int hptr = 0, nid = 0;

    auto make_line = [](const Entry& e) -> Line {
        return {e.h, e.pcost + min(e.la, e.h * (ll)e.lw) - e.h * ((ll)e.pw + e.lw), e.id};
    };
    auto hull_push = [&](Line ln) {
        while ((int)hull.size() - hptr >= 2 &&
               dominated(hull[hull.size()-2], hull[hull.size()-1], ln))
            hull.pop_back();
        hull.push_back(ln);
    };
    auto hull_pop_if = [&](int id) {
        if (!hull.empty() && hull.back().id == id) {
            hull.pop_back();
            if (hptr > (int)hull.size()) hptr = (int)hull.size();
        }
    };
    auto hull_query = [&](ll t) -> ll {
        while (hptr + 1 < (int)hull.size() &&
               hull[hptr+1].eval(t) <= hull[hptr].eval(t))
            hptr++;
        return hull[hptr].eval(t);
    };

    for (int j = base; j <= end; j++) {
        ll hj = H[j];
        ll right_ans = 0; int right_w = 0;

        while (!stk.empty() && stk.back().h <= hj) {
            auto& e = stk.back();
            int fw = e.lw + 1 + right_w;
            ll o1 = (e.lw > 0) ? e.la + e.h * (ll)(1 + right_w) : e.h * (ll)fw;
            ll o2 = e.h * (ll)fw;
            ll o3 = (right_w > 0) ? right_ans + e.h * (ll)(e.lw + 1) : e.h * (ll)fw;
            right_ans = min({o1, o2, o3}); right_w = fw;
            hull_pop_if(e.id); stk.pop_back();
        }

        ll pcost = 0; int pw = 0;
        if (!stk.empty()) {
            pcost = stk.back().pcost + stk.back().h * ((ll)stk.back().lw + 1);
            pw = stk.back().pw + stk.back().lw + 1;
        }
        int id = nid++;
        stk.push_back({hj, right_w, right_ans, pcost, pw, id});
        hull_push(make_line(stk.back()));
        dp[j - base] = hull_query(j - base + 1);
    }
    return dp;
}

// --- Stack-based dp: compute answer(base-k, base) extending left ---

vector<ll> build_dp_left(int start, int base) {
    int len = base - start + 1;
    vector<ll> dp(len);
    if (len == 0) return dp;

    struct Entry { ll h; int rw; ll ra; ll scost; int sw; int id; };
    vector<Entry> stk;
    deque<Line> hull;
    int hptr = 0, nid = 0;

    auto make_line = [](const Entry& e) -> Line {
        return {e.h, e.scost + min(e.ra, e.h * (ll)e.rw) - e.h * ((ll)e.sw + e.rw), e.id};
    };
    auto hull_push = [&](Line ln) {
        while ((int)hull.size() - hptr >= 2 &&
               dominated(hull[hull.size()-2], hull[hull.size()-1], ln))
            hull.pop_back();
        hull.push_back(ln);
    };
    auto hull_pop_if = [&](int id) {
        if (!hull.empty() && hull.back().id == id) {
            hull.pop_back();
            if (hptr > (int)hull.size()) hptr = (int)hull.size();
        }
    };
    auto hull_query = [&](ll t) -> ll {
        while (hptr + 1 < (int)hull.size() &&
               hull[hptr+1].eval(t) <= hull[hptr].eval(t))
            hptr++;
        return hull[hptr].eval(t);
    };

    for (int i = base; i >= start; i--) {
        ll hi = H[i];
        ll left_ans = 0; int left_w = 0;

        while (!stk.empty() && stk.back().h <= hi) {
            auto& e = stk.back();
            int fw = e.rw + 1 + left_w;
            ll o1 = (e.rw > 0) ? e.ra + e.h * (ll)(1 + left_w) : e.h * (ll)fw;
            ll o2 = e.h * (ll)fw;
            ll o3 = (left_w > 0) ? left_ans + e.h * (ll)(e.rw + 1) : e.h * (ll)fw;
            left_ans = min({o1, o2, o3}); left_w = fw;
            hull_pop_if(e.id); stk.pop_back();
        }

        ll scost = 0; int sw = 0;
        if (!stk.empty()) {
            scost = stk.back().scost + stk.back().h * ((ll)stk.back().rw + 1);
            sw = stk.back().sw + stk.back().rw + 1;
        }
        int id = nid++;
        stk.push_back({hi, left_w, left_ans, scost, sw, id});
        hull_push(make_line(stk.back()));
        dp[base - i] = hull_query(base - i + 1);
    }
    return dp;
}

// --- D&C on Cartesian tree ---

struct Query { int l, r, idx; };

void solve(int lo, int hi, vector<Query>& qs) {
    if (lo > hi || qs.empty()) return;
    int p = spt.query(lo, hi);

    vector<Query> leftQs, rightQs, spanQs;
    for (auto& q : qs) {
        if (q.r < p)       leftQs.push_back(q);
        else if (q.l > p)  rightQs.push_back(q);
        else               spanQs.push_back(q);
    }

    solve(lo, p - 1, leftQs);
    solve(p + 1, hi, rightQs);
    if (spanQs.empty()) return;

    ll hp = H[p];
    int leftLen = p - lo, rightLen = hi - p;

    // Only compute dp for the shorter side. For the longer side, compute
    // only the needed values (unique query endpoints).
    // This ensures O(N log N) total dp computation by the shorter-side argument.

    if (leftLen <= rightLen) {
        // Shorter: left. Compute dpL for all positions in [lo, p-1].
        vector<ll> dpL;
        if (p > lo) dpL = build_dp_left(lo, p - 1);
        // dpL[k] = answer(p-1-k, p-1), so answer(i, p-1) = dpL[p-1-i]

        // Longer: right. Compute dpR only for needed qr values.
        vector<int> need_R;
        for (auto& q : spanQs)
            if (q.r > p) need_R.push_back(q.r);
        sort(need_R.begin(), need_R.end());
        need_R.erase(unique(need_R.begin(), need_R.end()), need_R.end());

        // Build dpR up to the maximum needed qr.
        unordered_map<int, ll> dpR_map;
        if (!need_R.empty()) {
            int maxR = need_R.back();
            vector<ll> dpR = build_dp_right(p + 1, maxR);
            for (int r : need_R)
                dpR_map[r] = dpR[r - p - 1];
        }

        for (auto& q : spanQs) {
            ll opt2 = hp * (ll)(q.r - q.l + 1);
            ll opt1 = opt2, opt3 = opt2;
            if (q.l < p && !dpL.empty())
                opt1 = dpL[p - 1 - q.l] + hp * (ll)(q.r - p + 1);
            if (q.r > p)
                opt3 = dpR_map[q.r] + hp * (ll)(p - q.l + 1);
            ans[q.idx] = min({opt1, opt2, opt3});
        }
    } else {
        // Shorter: right. Compute dpR for all positions in [p+1, hi].
        vector<ll> dpR;
        if (p < hi) dpR = build_dp_right(p + 1, hi);
        // dpR[k] = answer(p+1, p+1+k), so answer(p+1, j) = dpR[j-p-1]

        // Longer: left. Compute dpL only for needed ql values.
        vector<int> need_L;
        for (auto& q : spanQs)
            if (q.l < p) need_L.push_back(q.l);
        sort(need_L.begin(), need_L.end());
        need_L.erase(unique(need_L.begin(), need_L.end()), need_L.end());

        unordered_map<int, ll> dpL_map;
        if (!need_L.empty()) {
            int minL = need_L.front();
            vector<ll> dpL = build_dp_left(minL, p - 1);
            for (int l : need_L)
                dpL_map[l] = dpL[p - 1 - l];
        }

        for (auto& q : spanQs) {
            ll opt2 = hp * (ll)(q.r - q.l + 1);
            ll opt1 = opt2, opt3 = opt2;
            if (q.l < p)
                opt1 = dpL_map[q.l] + hp * (ll)(q.r - p + 1);
            if (q.r > p && !dpR.empty())
                opt3 = dpR[q.r - p - 1] + hp * (ll)(p - q.l + 1);
            ans[q.idx] = min({opt1, opt2, opt3});
        }
    }
}

vector<long long> minimum_costs(vector<int> h, vector<int> l, vector<int> r) {
    H = h; N = h.size();
    int Q = l.size();
    spt.build(H);
    ans.resize(Q);
    vector<Query> qs(Q);
    for (int i = 0; i < Q; i++) qs[i] = {l[i], r[i], i};
    solve(0, N - 1, qs);
    return ans;
}

int main() {
    int n, q;
    scanf("%d %d", &n, &q);
    vector<int> h(n);
    for (int i = 0; i < n; i++) scanf("%d", &h[i]);
    vector<int> l(q), r(q);
    for (int i = 0; i < q; i++) scanf("%d %d", &l[i], &r[i]);
    auto res = minimum_costs(h, l, r);
    for (int i = 0; i < q; i++) printf("%lld\n", res[i]);
    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/2018/meetings/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}

\newtheorem{lemma}{Lemma}
\newtheorem{theorem}{Theorem}

\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 2018 -- Meetings}
\author{Solution}
\date{}

\begin{document}
\maketitle

\section{Problem Summary}

There are $n$ mountains with heights $H_0, \ldots, H_{n-1}$. For a query $(L, R)$, choose a meeting point $m \in [L, R]$ to minimize:
\[
  \text{cost}(m) = \sum_{i=L}^{R} \max_{j \in [\min(i,m),\, \max(i,m)]} H_j.
\]
Return the minimum cost.

\section{Solution}

\subsection{Cartesian Tree Decomposition}

Let $p$ be the position of the maximum in $[L, R]$ (found via sparse table in $O(1)$). Then:

\begin{itemize}
  \item If $m \in [L, p-1]$: everyone in $[p, R]$ must pass through position $p$, paying at least $H_p$ each. The cost splits as:
  \[
    \text{cost}(m) = \text{cost}_{[L,p-1]}(m) + H_p \cdot (R - p + 1)
  \]
  \item If $m \in [p+1, R]$: symmetrically, $\text{cost}(m) = \text{cost}_{[p+1,R]}(m) + H_p \cdot (p - L + 1)$.
  \item If $m = p$: $\text{cost}(p) = H_p \cdot (R - L + 1)$.
\end{itemize}

\begin{lemma}
$\textit{best}(L, R) = \min\bigl(\textit{best}(L, p-1) + H_p(R-p+1),\; \textit{best}(p+1, R) + H_p(p-L+1),\; H_p(R-L+1)\bigr)$
where $p = \arg\max_{i \in [L,R]} H_i$.
\end{lemma}

This recursion follows the Cartesian tree of $H$. For a single query, the depth of recursion equals the depth of the Cartesian tree, which is $O(n)$ worst case but $O(\log n)$ expected for random inputs.

\subsection{Full-Score Solution Outline}

For $O((n + q) \log^2 n)$ total time, process all queries offline on the Cartesian tree. At each Cartesian tree node with range $[L, R]$ and max at $p$, queries spanning $p$ decompose into a left-side cost plus a right-side cost. Use the ``smaller-to-larger'' technique: iterate over the smaller subtree and insert cost functions (linear functions of the meeting point) into a Li Chao tree for the larger subtree.

\section{Implementation}

The repository implementation follows the offline Cartesian-tree decomposition described above, with precomputed side costs so that all queries are handled efficiently.

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

struct SparseTable {
    int n;
    vector<vector<int>> tbl;
    vector<int> arr, lg;

    void build(vector<int>& a) {
        arr = a; n = a.size();
        lg.resize(n + 1);
        for (int i = 2; i <= n; i++) lg[i] = lg[i/2] + 1;
        int K = lg[n] + 1;
        tbl.assign(K, vector<int>(n));
        for (int i = 0; i < n; i++) tbl[0][i] = i;
        for (int k = 1; k < K; k++)
            for (int i = 0; i + (1 << k) <= n; i++) {
                int l = tbl[k-1][i], r = tbl[k-1][i + (1 << (k-1))];
                tbl[k][i] = (arr[l] >= arr[r]) ? l : r;
            }
    }

    int query(int l, int r) {
        int k = lg[r - l + 1];
        int a = tbl[k][l], b = tbl[k][r - (1 << k) + 1];
        return (arr[a] >= arr[b]) ? a : b;
    }
};

vector<long long> minimum_costs(vector<int> H, vector<int> L, vector<int> R) {
    int N = H.size(), Q = L.size();
    SparseTable spt;
    spt.build(H);

    function<ll(int, int)> solve = [&](int l, int r) -> ll {
        if (l > r) return 0;
        if (l == r) return H[l];
        int p = spt.query(l, r);
        ll leftSize = p - l, rightSize = r - p;
        ll opt = (ll)H[p] * (r - l + 1);  // meet at p
        if (leftSize > 0)
            opt = min(opt, solve(l, p - 1) + (ll)H[p] * (rightSize + 1));
        if (rightSize > 0)
            opt = min(opt, solve(p + 1, r) + (ll)H[p] * (leftSize + 1));
        return opt;
    };

    vector<ll> ans(Q);
    for (int q = 0; q < Q; q++)
        ans[q] = solve(L[q], R[q]);
    return ans;
}
\end{lstlisting}

\section{Complexity Analysis}

\begin{itemize}
  \item \textbf{Sparse table}: $O(n \log n)$ preprocessing, $O(1)$ per range-max query.
  \item \textbf{Per query (recursive)}: $O(n)$ worst case (degenerate Cartesian tree), $O(\log n)$ expected.
  \item \textbf{Full-score offline}: $O((n + q) \log^2 n)$ using Li Chao trees on the Cartesian tree with smaller-to-larger merging.
\end{itemize}

\end{document}