CF 1028C - Rectangles
We are given a collection of axis-aligned rectangles on a 2D integer grid. Each rectangle is described by its bottom-left corner and top-right corner, and it includes its boundary as well as its interior.
Rating: 1600
Tags: geometry, implementation, sortings
Solve time: 2m 30s
Verified: yes
Solution
Problem Understanding
We are given a collection of axis-aligned rectangles on a 2D integer grid. Each rectangle is described by its bottom-left corner and top-right corner, and it includes its boundary as well as its interior. The key guarantee is that if we remove any one rectangle, the remaining rectangles still share at least one common point.
The task is to find any integer coordinate point that lies inside at least $n-1$ of these rectangles. In other words, we are allowed to “miss” at most one rectangle, and we must still be inside all others simultaneously.
The constraints are large, with $n$ up to about $1.3 \times 10^5$. This immediately rules out any quadratic interaction between rectangles. Even $O(n^2)$ pairwise overlap reasoning is far beyond feasible limits. We are pushed toward solutions that maintain global constraints incrementally or recompute intersections in linear or near-linear time.
A subtle edge case comes from the fact that the answer only needs to satisfy all but one rectangle. A naive intersection of all rectangles can fail even when a valid answer exists, because the single “bad” rectangle can shrink the global intersection to empty.
For example, consider three rectangles where two overlap heavily and the third slightly shifts the intersection:
Input:
3
0 0 2 2
1 1 3 3
100 100 200 200
The intersection of all three is empty, but removing the third rectangle leaves a valid overlap region between the first two, and any point in that region is acceptable.
A second pitfall is assuming that the intersection of any fixed subset works. The problem does not tell us which rectangle is the outlier, so we must be able to “simulate” removing each one implicitly without recomputing everything from scratch.
Approaches
The brute-force idea starts naturally from the condition. If we try removing each rectangle one by one, we can compute the intersection of the remaining $n-1$ rectangles and check whether it is non-empty. Computing an intersection of rectangles is simple: we take the maximum of all left x-coordinates, the minimum of all right x-coordinates, and similarly for y.
Doing this independently for every removed rectangle leads to $n$ recomputations. Each recomputation scans all rectangles, giving $O(n^2)$ total operations. With $n \approx 10^5$, this is far too slow.
The key observation is that each intersection query depends on global extrema: the maximum and second maximum of left edges, minimum and second minimum of right edges, and the same structure for y. When we remove one rectangle, only the extremal contributions from that rectangle matter. If it was not responsible for a boundary, the global intersection stays unchanged. If it was responsible, the second-best value becomes active.
This reduces the problem to maintaining prefix and suffix information over sorted extremes. We can precompute, for each direction, both the best and second-best contributions along with counts. Then, for each rectangle, we can reconstruct what the intersection would look like if it were removed in constant time.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O(n^2)$ | $O(1)$ | Too slow |
| Optimal | $O(n)$ | $O(n)$ | Accepted |
Algorithm Walkthrough
We process all rectangles while maintaining global and “backup” extrema.
- For all rectangles, compute four global quantities for each dimension: the largest left boundary, smallest right boundary, and track how many rectangles achieve each of these extrema. We also track second-best values so we can recover bounds when one rectangle is removed.
The reason we need second-best values is that removing a rectangle that defines an extreme boundary would otherwise make us lose that constraint entirely. 2. Repeat the same logic independently for x and y dimensions. Each dimension behaves independently because the intersection condition factorizes. 3. For each rectangle, simulate its removal. If it is not contributing to an extreme, the global intersection remains unchanged. If it is contributing and it is the only contributor, we switch to the second-best value; otherwise the extreme stays unchanged. 4. After simulating removal of a rectangle, we obtain a candidate intersection rectangle defined by:
left = max_left_excluding_i, right = min_right_excluding_i,
bottom = max_bottom_excluding_i, top = min_top_excluding_i.
If this rectangle is valid (left ≤ right and bottom ≤ top), we immediately pick any integer point inside it. 5. Since the problem guarantees existence, at least one rectangle removal yields a non-empty intersection, so we will find an answer.
Why it works
The correctness rests on the fact that the intersection of axis-aligned rectangles is fully determined by independent constraints on x and y intervals. For each dimension, only two values matter: the maximum left boundary and the minimum right boundary. Removing one rectangle can only affect these two values. By storing second-best candidates, we preserve the correct boundary under any single removal. Thus every “all but one” intersection is computed exactly without recomputing from scratch.
Python Solution
import sys
input = sys.stdin.readline
def solve():
n = int(input())
xs1, ys1, xs2, ys2 = [], [], [], []
for _ in range(n):
x1, y1, x2, y2 = map(int, input().split())
xs1.append(x1)
ys1.append(y1)
xs2.append(x2)
ys2.append(y2)
def prep_max(arr):
max1 = -10**18
max2 = -10**18
cnt1 = 0
for v in arr:
if v > max1:
max2 = max1
max1 = v
cnt1 = 1
elif v == max1:
cnt1 += 1
elif v > max2:
max2 = v
return max1, max2, cnt1
def prep_min(arr):
min1 = 10**18
min2 = 10**18
cnt1 = 0
for v in arr:
if v < min1:
min2 = min1
min1 = v
cnt1 = 1
elif v == min1:
cnt1 += 1
elif v < min2:
min2 = v
return min1, min2, cnt1
xL1, xL2, xLcnt = prep_max(xs1)
yL1, yL2, yLcnt = prep_max(ys1)
xR1, xR2, xRcnt = prep_min(xs2)
yR1, yR2, yRcnt = prep_min(ys2)
for i in range(n):
l = xL2 if xs1[i] == xL1 and xLcnt == 1 else xL1
r = xR2 if xs2[i] == xR1 and xRcnt == 1 else xR1
d = yL2 if ys1[i] == yL1 and yLcnt == 1 else yL1
u = yR2 if ys2[i] == yR1 and yRcnt == 1 else yR1
if l <= r and d <= u:
print(l, d)
return
print(0, 0)
if __name__ == "__main__":
solve()
The solution first compresses each rectangle into four arrays representing boundaries. It then computes global extrema for left, right, bottom, and top edges, along with second-best values and counts to detect whether the extremal value depends on a unique rectangle.
During the removal simulation, we check whether rectangle $i$ is uniquely responsible for any extreme boundary. If so, we fall back to the second-best value; otherwise we keep the global extreme unchanged. This is done independently for x and y, forming a candidate intersection rectangle.
Once a valid intersection is found, we output its bottom-left corner. Any point inside the intersection works, so choosing the lower-left corner avoids additional computation.
Worked Examples
Example 1
Input:
3
0 0 1 1
1 1 2 2
3 0 4 1
We compute:
| Step | xL | xR | yL | yR | Valid? |
|---|---|---|---|---|---|
| all rectangles | 0 | 1 | 1 | 1 | no |
| remove rect 1 | 1 | 2 | 1 | 2 | yes |
| remove rect 2 | 0 | 1 | 0 | 1 | yes |
| remove rect 3 | 0 | 2 | 1 | 2 | no |
The first valid configuration gives intersection [1,1], so we output:
1 1
This shows how only one rectangle needs to be removed to restore a non-empty overlap.
Example 2
Input:
2
0 0 5 5
1 1 4 4
| Step | xL | xR | yL | yR | Valid? |
|---|---|---|---|---|---|
| remove rect 1 | 1 | 4 | 1 | 4 | yes |
| remove rect 2 | 0 | 5 | 0 | 5 | yes |
Any point in either full intersection works; both rectangles already overlap fully, so removing either still leaves a valid region.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(n)$ | single pass to compute extrema and single scan to test removals |
| Space | $O(n)$ | storing rectangle boundaries |
The algorithm performs only linear scans and constant-time checks per rectangle, which fits comfortably within limits for $n \le 1.3 \times 10^5$.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
import sys
input = sys.stdin.readline
n = int(input())
xs1, ys1, xs2, ys2 = [], [], [], []
for _ in range(n):
x1, y1, x2, y2 = map(int, input().split())
xs1.append(x1)
ys1.append(y1)
xs2.append(x2)
ys2.append(y2)
def prep_max(arr):
max1 = -10**18
max2 = -10**18
cnt1 = 0
for v in arr:
if v > max1:
max2 = max1
max1 = v
cnt1 = 1
elif v == max1:
cnt1 += 1
elif v > max2:
max2 = v
return max1, max2, cnt1
def prep_min(arr):
min1 = 10**18
min2 = 10**18
cnt1 = 0
for v in arr:
if v < min1:
min2 = min1
min1 = v
cnt1 = 1
elif v == min1:
cnt1 += 1
elif v < min2:
min2 = v
return min1, min2, cnt1
xL1, xL2, xLcnt = prep_max(xs1)
yL1, yL2, yLcnt = prep_max(ys1)
xR1, xR2, xRcnt = prep_min(xs2)
yR1, yR2, yRcnt = prep_min(ys2)
for i in range(n):
l = xL2 if xs1[i] == xL1 and xLcnt == 1 else xL1
r = xR2 if xs2[i] == xR1 and xRcnt == 1 else xR1
d = yL2 if ys1[i] == yL1 and yLcnt == 1 else yL1
u = yR2 if ys2[i] == yR1 and yRcnt == 1 else yR1
if l <= r and d <= u:
return f"{l} {d}"
return "0 0"
# provided sample
assert run("""3
0 0 1 1
1 1 2 2
3 0 4 1
""") == "1 1"
# custom 1: minimal
assert run("""2
0 0 1 1
0 0 1 1
""") == "0 0"
# custom 2: one outlier
assert run("""3
0 0 10 10
1 1 2 2
1 1 2 2
""") == "1 1"
# custom 3: extreme separation
assert run("""3
0 0 1 1
0 0 1 1
100 100 200 200
""") in {"0 0", "1 1"}
# custom 4: boundary touch
assert run("""2
0 0 1 1
1 1 2 2
""") == "1 1"
| Test input | Expected output | What it validates |
|---|---|---|
| minimal duplicate rectangles | shared point | base correctness |
| one outlier rectangle | valid overlap | removal logic |
| separated rectangle | any valid remaining intersection | robustness |
| boundary touching rectangles | corner inclusion | boundary handling |
Edge Cases
One important case is when the maximum left boundary is defined by a single rectangle. For example:
3
5 0 10 10
0 0 6 6
0 0 6 6
If the first rectangle is removed, the maximum left boundary drops from 5 to 0. The algorithm handles this by checking the count of maximum contributors and switching to the second maximum only when needed. In this case, removing rectangle 1 immediately changes the left boundary to 0, producing a valid intersection with the remaining rectangles.
Another case is when both extreme boundaries come from the same rectangle. The algorithm still works because it independently adjusts left and right bounds. Even if one rectangle influences both sides, removing it replaces both values with second-best candidates, preserving a correct intersection region.
A final edge case is when all rectangles already overlap. Then every removal still yields the same global intersection. The algorithm will detect validity immediately on the first iteration without needing to rely on second-best values.