IOI 2006
IOI 2006

Pyramid

2D Prefix Sums Precompute the 2D prefix-sum array so that any rectangle sum can be queried in O(1): S[i][j] = _ r=1 ^ i _ c=1 ^ j grid[r][c]. Then: rectSum(r_1, c_1, r_2, c_2) = S[r_2][c_2] - S[r_1 - 1][c_2] - S[r_2][...

Updated May 21, 2026
Track IOI
Year 2006
Statement Rendered from TeX
TeXC++Rendered statement

Problem Statement

Rendered from the "Problem Statement" section in the LaTeX write-up.

Given an $R \times C$ grid of integers, find two axis-aligned rectangles -- an outer rectangle of dimensions $a \times b$ and an inner rectangle of dimensions $c \times d$ -- such that the inner rectangle is strictly contained in the outer rectangle (with at least one row/column of margin on each side), and the border sum (sum of the outer rectangle minus the sum of the inner rectangle) is maximized.

Editorial

The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.

Solution

2D Prefix Sums

Precompute the 2D prefix-sum array so that any rectangle sum can be queried in $O(1)$: \[ S[i][j] = \sum_{r=1}^{i} \sum_{c=1}^{j} \text{grid}[r][c]. \] Then: \[ \text{rectSum}(r_1, c_1, r_2, c_2) = S[r_2][c_2] - S[r_1{-}1][c_2] - S[r_2][c_1{-}1] + S[r_1{-}1][c_1{-}1]. \]

Reducing to a 2D Sliding-Window Minimum

For a fixed outer rectangle with top-left corner at $(r, c)$, the border sum is: \[ \text{rectSum}(r, c, r{+}a{-}1, c{+}b{-}1) - \min_{\substack{(r', c') \text{ s.t. inner} \\ \text{fits inside outer}}} \text{rectSum}(r', c', r'{+}c{-}1, c'{+}d{-}1). \] The inner top-left $(r', c')$ ranges over $[r+1,\; r+a-c-1] \times [c+1,\; c+b-d-1]$.

To maximize the border sum, for each outer position we need the minimum inner sum. This is a 2D sliding-window minimum problem.

2D Sliding Minimum via Two Passes of 1D Deque

  1. Precompute inner sums. For each valid $(i,j)$, let $I[i][j] = \text{rectSum}(i, j, i{+}c{-}1, j{+}d{-}1)$.

  2. Row-wise sliding minimum. For each row $i$, compute the sliding minimum of $I[i][j]$ over windows of width $W_c = b - d - 1$ using a deque. Store results in $\text{rowMin}[i][j]$.

  3. Column-wise sliding minimum. For each column $j$, compute the sliding minimum of $\text{rowMin}[i][j]$ over windows of height $W_r = a - c - 1$ using a deque. This gives the 2D sliding minimum $\text{minInner}[i][j]$.

  4. Combine. For each outer position $(r, c)$, the answer candidate is $\text{outerSum}(r, c) - \text{minInner}[r+1][c+1]$.

  5. Each element enters and leaves each deque at most once, so the total work is $O(R \times C)$.

Complexity

  • Time: $O(R \times C)$.

  • Space: $O(R \times C)$.

C++ Solution

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

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int R, C;
    cin >> R >> C;

    vector<vector<long long>> S(R + 1, vector<long long>(C + 1, 0));
    for(int i = 1; i <= R; i++)
        for(int j = 1; j <= C; j++){
            int v; cin >> v;
            S[i][j] = v + S[i-1][j] + S[i][j-1] - S[i-1][j-1];
        }

    auto rectSum = [&](int r1, int c1, int r2, int c2) -> long long {
        return S[r2][c2] - S[r1-1][c2] - S[r2][c1-1] + S[r1-1][c1-1];
    };

    int a, b, c, d;
    cin >> a >> b >> c >> d;

    // Inner window dimensions for the sliding minimum
    int wh = a - c - 1; // number of valid inner row offsets
    int ww = b - d - 1; // number of valid inner column offsets
    int maxOuterR = R - a + 1;
    int maxOuterC = C - b + 1;

    if(maxOuterR < 1 || maxOuterC < 1){
        cout << "No valid placement\n";
        return 0;
    }

    // Precompute inner rectangle sums
    int innerMaxR = R - c + 1;
    int innerMaxC = C - d + 1;
    vector<vector<long long>> I(innerMaxR + 1, vector<long long>(innerMaxC + 1));
    for(int i = 1; i <= innerMaxR; i++)
        for(int j = 1; j <= innerMaxC; j++)
            I[i][j] = rectSum(i, j, i + c - 1, j + d - 1);

    // Step 1: row-wise sliding minimum (window width ww + 1)
    int winW = ww + 1;
    vector<vector<long long>> rowMin(innerMaxR + 1, vector<long long>(maxOuterC + 1, LLONG_MAX));
    for(int i = 1; i <= innerMaxR; i++){
        deque<int> dq;
        for(int j = 1; j <= innerMaxC; j++){
            while(!dq.empty() && I[i][dq.back()] >= I[i][j])
                dq.pop_back();
            dq.push_back(j);
            if(dq.front() < j - winW + 1) dq.pop_front();
            if(j >= winW){
                // The outer column for this entry: outer_c = j - winW + 1 - 1 + 1
                // Inner col starts at outer_c + 1, range [outer_c+1 .. outer_c+ww+1]
                // j - winW + 1 is the first inner col in window, map to outer col = (j - winW + 1) - 1
                int oc = j - winW; // 1-based outer column
                if(oc >= 1 && oc <= maxOuterC)
                    rowMin[i][oc] = I[i][dq.front()];
            }
        }
    }

    // Step 2: column-wise sliding minimum (window height wh + 1) on rowMin
    int winH = wh + 1;
    long long ans = LLONG_MIN;

    for(int oc = 1; oc <= maxOuterC; oc++){
        deque<int> dq;
        for(int i = 1; i <= innerMaxR; i++){
            if(rowMin[i][oc] == LLONG_MAX) continue;
            while(!dq.empty() && rowMin[dq.back()][oc] >= rowMin[i][oc])
                dq.pop_back();
            dq.push_back(i);
            if(dq.front() < i - winH + 1) dq.pop_front();
            if(i >= winH){
                int or_ = i - winH; // 1-based outer row
                if(or_ >= 1 && or_ <= maxOuterR){
                    long long outerS = rectSum(or_, oc, or_ + a - 1, oc + b - 1);
                    long long minInner = rowMin[dq.front()][oc];
                    ans = max(ans, outerS - minInner);
                }
            }
        }
    }

    cout << ans << "\n";
    return 0;
}

Notes

The 2D sliding-window minimum is the core technique. It decomposes into two passes of the classical 1D sliding-window minimum (using a monotone deque), each amortized $O(1)$ per element.

The index arithmetic requires care: the inner rectangle's top-left must satisfy $r' \in [r+1,\; r + a - c - 1]$ and $c' \in [c+1,\; c + b - d - 1]$ for outer top-left $(r, c)$.

Code

C++ solution used for this page.

C++

Clean code view with a raw-file link when you want the original source.

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

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int R, C;
    cin >> R >> C;

    vector<vector<long long>> S(R + 1, vector<long long>(C + 1, 0));
    for(int i = 1; i <= R; i++)
        for(int j = 1; j <= C; j++){
            int v; cin >> v;
            S[i][j] = v + S[i-1][j] + S[i][j-1] - S[i-1][j-1];
        }

    auto rectSum = [&](int r1, int c1, int r2, int c2) -> long long {
        if(r1 > r2 || c1 > c2) return 0;
        return S[r2][c2] - S[r1-1][c2] - S[r2][c1-1] + S[r1-1][c1-1];
    };

    int a, b, c, d; // outer a x b, inner c x d
    cin >> a >> b >> c >> d;

    // inner rectangle sum at position (i, j) = rectSum(i, j, i+c-1, j+d-1)
    // Precompute inner sums
    int innerRows = R - c + 1;
    int innerCols = C - d + 1;

    // For each outer rectangle at (r, c_pos):
    // Inner must have top-left in [r+1, r+a-c-1] x [c_pos+1, c_pos+b-d-1]
    // (1-based, inner fits strictly inside outer)

    // Height of inner window range: a - c - 1 rows
    // Width of inner window range: b - d - 1 columns
    int winH = a - c - 1;
    int winW = b - d - 1;

    if(winH < 0 || winW < 0){
        // Inner doesn't fit, just maximize outer
        long long ans = LLONG_MIN;
        for(int i = 1; i + a - 1 <= R; i++)
            for(int j = 1; j + b - 1 <= C; j++)
                ans = max(ans, rectSum(i, j, i+a-1, j+b-1));
        cout << ans << "\n";
        return 0;
    }

    // Compute inner sums for all valid positions
    vector<vector<long long>> innerS(R + 1, vector<long long>(C + 1, 0));
    for(int i = 1; i + c - 1 <= R; i++)
        for(int j = 1; j + d - 1 <= C; j++)
            innerS[i][j] = rectSum(i, j, i + c - 1, j + d - 1);

    // 2D sliding window minimum of innerS
    // Window size: (winH + 1) x (winW + 1)
    int wh = winH + 1, ww = winW + 1;

    // Step 1: row-wise sliding minimum
    vector<vector<long long>> rowMin(R + 1, vector<long long>(C + 1, LLONG_MAX));
    for(int i = 1; i + c - 1 <= R; i++){
        deque<int> dq;
        for(int j = 1; j + d - 1 <= C; j++){
            while(!dq.empty() && innerS[i][dq.back()] >= innerS[i][j])
                dq.pop_back();
            dq.push_back(j);
            if(dq.front() < j - ww + 1) dq.pop_front();
            if(j >= ww)
                rowMin[i][j - ww + 1] = innerS[i][dq.front()];
        }
    }

    // Step 2: column-wise sliding minimum on rowMin
    long long ans = LLONG_MIN;
    int maxOuterR = R - a + 1;
    int maxOuterC = C - b + 1;

    for(int j = 1; j <= maxOuterC; j++){
        deque<int> dq;
        // Column j in rowMin corresponds to inner starting column j+1
        // (since inner starts at outer_col + 1)
        // Actually, inner top-left ranges in [outer_r+1 .. outer_r+winH+1] row-wise
        // and [outer_c+1 .. outer_c+winW+1] col-wise.
        // For outer at (r, j), inner col starts from j+1, row from r+1.
        // rowMin[i][j+1] has the min over columns j+1 .. j+ww.
        // We need min over rows i = r+1 .. r+wh.

        for(int i = 1; i + c - 1 <= R; i++){
            long long val = rowMin[i][j + 1]; // might be out of bounds, check
            if(j + 1 > (int)rowMin[i].size() - 1) continue;
            while(!dq.empty() && rowMin[dq.back()][j+1] >= val)
                dq.pop_back();
            dq.push_back(i);
            if(dq.front() < i - wh + 1) dq.pop_front();
            if(i >= wh){
                int outerR = i - wh; // 0-indexed? Let's be careful
                // outer top-left row: inner must start from outerR+1
                // and inner row range is [outerR+1 .. outerR+wh]
                // so i ranges from outerR+1 to outerR+wh
                // i = outerR+wh => outerR = i - wh + 1 - 1 = i - wh
                // Hmm, let me just compute:
                int or_ = i - wh + 1 - 1; // outer row (1-based)
                if(or_ >= 1 && or_ + a - 1 <= R){
                    long long outerSum = rectSum(or_, j, or_ + a - 1, j + b - 1);
                    long long minInner = rowMin[dq.front()][j + 1];
                    ans = max(ans, outerSum - minInner);
                }
            }
        }
    }

    cout << ans << "\n";
    return 0;
}

Source Files and Assets

Raw files are still available here when you want the original TeX, C++, or statement assets.

Show raw files