Pyramid Base
Binary Search on Side Length [Monotonicity] If a square of side s can be placed with cost B, then any square of side s' < s can be placed at the same position with cost B (it overlaps a subset of the same obstacles)....
Problem Statement
Rendered from the "Problem Statement" section in the LaTeX write-up.
Given an $R \times C$ grid with $P$ rectangular obstacles (each with a removal cost) and a budget $B$, find the largest side length $s$ of an axis-aligned square that can be placed on the grid such that the total cost of overlapping obstacles is at most $B$.
Editorial
The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.
Solution
Binary Search on Side Length
Lemma (Monotonicity).
If a square of side $s$ can be placed with cost $\le B$, then any square of side $s' < s$ can be placed at the same position with cost $\le B$ (it overlaps a subset of the same obstacles). This justifies binary search on $s$.
Feasibility Check via Sweep Line
For a given side length $s$, determine if any placement has cost $\le B$:
For each obstacle $i$ with bounding box $[x_1^i, y_1^i] \times [x_2^i, y_2^i]$ and cost $c_i$, the set of top-left corners $(r, c)$ whose $s \times s$ square overlaps this obstacle forms a rectangle. Specifically: \[ r \in [\max(1, x_1^i - s + 1),\; \min(R - s + 1, x_2^i)], \quad c \in [\max(1, y_1^i - s + 1),\; \min(C - s + 1, y_2^i)]. \]
Create sweep-line events: at row $r_{\text{start}}$, add $c_i$ to the column range; at row $r_{\text{end}}+1$, subtract $c_i$.
Sweep rows top to bottom, maintaining a segment tree supporting range-add and global-minimum queries. If the global minimum $\le B$ at any row, side length $s$ is feasible.
Complexity
Time: $O\bigl(\log(\min(R,C)) \cdot (R + P) \cdot \log C\bigr)$.
Space: $O(R + C + P)$.
C++ Solution
#include <bits/stdc++.h>
using namespace std;
struct SegTree {
int n;
vector<long long> mn, lazy;
SegTree(int n) : n(n), mn(4*n, 0), lazy(4*n, 0) {}
void push(int v) {
if (lazy[v]) {
for (int c : {2*v, 2*v+1}) {
mn[c] += lazy[v];
lazy[c] += lazy[v];
}
lazy[v] = 0;
}
}
void update(int v, int l, int r, int ql, int qr, long long val) {
if (ql > r || qr < l) return;
if (ql <= l && r <= qr) {
mn[v] += val; lazy[v] += val; return;
}
push(v);
int mid = (l + r) / 2;
update(2*v, l, mid, ql, qr, val);
update(2*v+1, mid+1, r, ql, qr, val);
mn[v] = min(mn[2*v], mn[2*v+1]);
}
void update(int ql, int qr, long long val) {
if (ql > qr) return;
update(1, 1, n, ql, qr, val);
}
long long query() { return mn[1]; }
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int R, C, P;
long long B;
cin >> R >> C >> P >> B;
vector<int> x1(P), y1(P), x2(P), y2(P);
vector<long long> cost(P);
for (int i = 0; i < P; i++)
cin >> x1[i] >> y1[i] >> x2[i] >> y2[i] >> cost[i];
int lo = 1, hi = min(R, C), ans = 0;
while (lo <= hi) {
int s = (lo + hi) / 2;
int maxR = R - s + 1, maxC = C - s + 1;
if (maxR < 1 || maxC < 1) { hi = s - 1; continue; }
// Create events: events[row] = list of (col_start, col_end, delta)
vector<vector<tuple<int,int,long long>>> events(maxR + 2);
for (int i = 0; i < P; i++) {
int rs = max(1, x1[i] - s + 1);
int re = min(maxR, x2[i]);
if (rs > re) continue;
int cs = max(1, y1[i] - s + 1);
int ce = min(maxC, y2[i]);
if (cs > ce) continue;
events[rs].emplace_back(cs, ce, cost[i]);
if (re + 1 <= maxR)
events[re + 1].emplace_back(cs, ce, -cost[i]);
}
SegTree seg(maxC);
bool feasible = false;
for (int r = 1; r <= maxR && !feasible; r++) {
for (auto& [cs, ce, delta] : events[r])
seg.update(cs, ce, delta);
if (seg.query() <= B) feasible = true;
}
if (feasible) { ans = s; lo = s + 1; }
else hi = s - 1;
}
cout << ans << "\n";
return 0;
}
Notes
The segment tree supports lazy range-add and global-minimum queries. Each obstacle creates two sweep-line events (entry and exit). The total work per binary-search iteration is $O((R + P) \log C)$, giving an efficient overall solution.
For $B = 0$, the problem reduces to finding the largest obstacle-free square, which this approach handles as a special case.
Code
C++ solution used for this page.
#include <bits/stdc++.h>
using namespace std;
struct SegTree {
int n;
vector<long long> mn, lazy;
SegTree(int n) : n(n), mn(4 * n, 0), lazy(4 * n, 0) {}
void push(int v) {
if (lazy[v] != 0) {
for (int c : {2*v, 2*v+1}) {
mn[c] += lazy[v];
lazy[c] += lazy[v];
}
lazy[v] = 0;
}
}
void update(int v, int l, int r, int ql, int qr, long long val) {
if (ql > r || qr < l) return;
if (ql <= l && r <= qr) {
mn[v] += val;
lazy[v] += val;
return;
}
push(v);
int mid = (l + r) / 2;
update(2*v, l, mid, ql, qr, val);
update(2*v+1, mid+1, r, ql, qr, val);
mn[v] = min(mn[2*v], mn[2*v+1]);
}
long long query() { return mn[1]; }
void update(int ql, int qr, long long val) {
if (ql > qr) return;
update(1, 1, n, ql, qr, val);
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int R, C, P;
long long B;
cin >> R >> C >> P >> B;
vector<int> x1(P), y1(P), x2(P), y2(P);
vector<long long> cost(P);
for (int i = 0; i < P; i++) {
cin >> x1[i] >> y1[i] >> x2[i] >> y2[i] >> cost[i];
}
// Binary search on side length s
int lo = 1, hi = min(R, C), ans = 0;
while (lo <= hi) {
int s = (lo + hi) / 2;
// Check feasibility: can we place an s x s square with total cost <= B?
int maxR = R - s + 1; // top-left row range: 1..maxR
int maxC = C - s + 1; // top-left col range: 1..maxC
if (maxR < 1 || maxC < 1) {
hi = s - 1;
continue;
}
// Create events
// events[r] = list of (col_start, col_end, cost_delta)
vector<vector<tuple<int,int,long long>>> events(maxR + 2);
for (int i = 0; i < P; i++) {
int rs = max(1, x1[i] - s + 1);
int re = min(maxR, x2[i]);
if (rs > re) continue;
int cs = max(1, y1[i] - s + 1);
int ce = min(maxC, y2[i]);
if (cs > ce) continue;
events[rs].push_back({cs, ce, cost[i]});
if (re + 1 <= maxR)
events[re + 1].push_back({cs, ce, -cost[i]});
}
// Sweep
SegTree seg(maxC);
bool feasible = false;
for (int r = 1; r <= maxR; r++) {
for (auto& [cs, ce, delta] : events[r]) {
seg.update(cs, ce, delta);
}
if (seg.query() <= B) {
feasible = true;
break;
}
}
if (feasible) {
ans = s;
lo = s + 1;
} else {
hi = s - 1;
}
}
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.