CF 105047C - Rectangle
We are interacting with a hidden axis-aligned rectangle whose side lengths are two unknown integers $a$ and $b$, both at most 100. We cannot see these values directly.
Rating: -
Tags: -
Solve time: 51s
Verified: yes
Solution
Problem Understanding
We are interacting with a hidden axis-aligned rectangle whose side lengths are two unknown integers $a$ and $b$, both at most 100. We cannot see these values directly. Instead, we can probe the rectangle by proposing another rectangle with sides $c$ and $d$, and the judge tells us whether this $c \times d$ rectangle can be placed entirely inside the hidden $a \times b$ rectangle.
The response is deterministic: it is positive exactly when both $c \le a$ and $d \le b$, or when the orientation is swapped and $c \le b$ and $d \le a$. We are allowed up to 20 such queries, each using dimensions up to 200, and we must eventually output the two side lengths in any order.
The key difficulty is that we are not told which side corresponds to which dimension, and we only get a binary feasibility oracle. This makes the problem a reconstruction task over a monotone predicate in two variables.
A naive approach would try to test all pairs $(a, b)$ in the allowed range and check consistency with answers, but this already hints at inefficiency in an interactive setting where each query is costly. Even more importantly, naive reasoning risks ambiguity because the predicate is symmetric: swapping sides produces identical answers.
The main subtle edge case is symmetry. For example, if the hidden rectangle is $6 \times 4$, then querying $4 \times 6$ and $6 \times 4$ both return success, so any reconstruction strategy must treat orientation as irrelevant and only recover the multiset ${a, b}$.
Approaches
The interaction defines a monotone condition: if a rectangle $c \times d$ fits, then any $c' \le c$, $d' \le d$ also fits. This suggests that we can recover boundary information using queries that gradually approach the true dimensions.
A brute-force strategy would attempt to test all pairs $(c, d)$ in the range $1 \le c, d \le 200$. For each pair, we would ask the judge whether it fits and collect all valid candidates. However, this does not exploit structure. It would require up to 40,000 queries in the worst case, which is far beyond the limit of 20. Even if we restricted ourselves to checking feasibility of each candidate pair logically, we would still need too many queries to disambiguate the correct rectangle.
The key observation is that we can decouple the two dimensions. Suppose we can determine the maximum possible side length in one direction that still fits a fixed probe. If we fix one side, say $c = 200$, and binary search over $d$, we can find the largest $d$ such that $200 \times d$ fits. That value must be $\min(a, b)$ or at least constrained by the smaller side. Once we identify one side length, we can similarly deduce the other.
A cleaner perspective is that we only need to recover both dimensions as an unordered pair. We can first determine the maximum side length that fits as a square, which gives $\min(a, b)$. Then we fix a probe using that value and search for the maximum compatible second side, which reveals $\max(a, b)$.
This reduces the problem from a two-dimensional search to two one-dimensional monotone searches, each solvable with binary search in at most 8 queries.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | $O(200^2)$ queries | $O(1)$ | Too slow |
| Optimal | $O(\log 200)$ queries | $O(1)$ | Accepted |
Algorithm Walkthrough
We rely on the fact that feasibility is monotone in both dimensions and symmetric in orientation.
- First, we determine the smaller side of the rectangle by probing squares $k \times k$. We binary search the largest $k$ such that the answer is positive. This works because any square that fits must satisfy $k \le \min(a, b)$, and the property is monotone in $k$.
- Let this value be $s = \min(a, b)$. We now know one dimension up to order, but we still need the larger side.
- To find the second side, we fix a probe using $s$. We now binary search the largest $x$ such that $s \times x$ fits. If $s$ matches the smaller side, then $x$ will expand up to $\max(a, b)$. If we accidentally matched the larger side first, symmetry ensures the same reasoning still holds.
- After finding $x$, we output $(s, x)$.
The crucial reasoning step is that once one side is fixed at the smaller dimension, the feasibility predicate collapses into a one-dimensional monotone function in the other variable.
Why it works
The correctness relies on two monotonicities. First, if a square of size $k$ fits, then all smaller squares fit, which makes binary search valid. Second, once we fix one side to a value that does not exceed both $a$ and $b$, feasibility in the other dimension remains monotone and independent of orientation. This guarantees that binary search isolates the true boundary without ambiguity.
Python Solution
import sys
input = sys.stdin.readline
def ask(c, d):
print("?", c, d)
sys.stdout.flush()
res = input().strip()
if res == "-1":
sys.exit(0)
return int(res)
def solve():
lo, hi = 1, 200
best = 1
while lo <= hi:
mid = (lo + hi) // 2
if ask(mid, mid):
best = mid
lo = mid + 1
else:
hi = mid - 1
s = best
lo, hi = 1, 200
best2 = 1
while lo <= hi:
mid = (lo + hi) // 2
if ask(s, mid):
best2 = mid
lo = mid + 1
else:
hi = mid - 1
print("!", s, best2)
sys.stdout.flush()
if __name__ == "__main__":
solve()
The solution is structured around a single query helper that enforces flushing and handles the termination condition. The first binary search probes only squares, which avoids dealing with orientation entirely. The second search fixes one side and expands the other, relying on monotonicity of containment.
A subtle implementation detail is that we always update the best known value even after successful queries. This ensures correctness even if the binary search overshoots near the boundary. Another important detail is immediate termination on -1, since continuing would be judged as invalid interaction.
Worked Examples
We simulate two hidden rectangles.
First example: $a, b = 18, 12$.
| Step | Query | Response | Best square | s | best2 |
|---|---|---|---|---|---|
| 1 | 10×10 | 1 | 10 | 10 | - |
| 2 | 15×15 | 0 | 10 | 10 | - |
| 3 | 13×13 | 0 | 10 | 10 | - |
| 4 | 11×11 | 1 | 11 | 11 | - |
| 5 | 12×12 | 1 | 12 | 12 | - |
| 6 | 13×13 | 0 | 12 | 12 | - |
Now second phase:
| Step | Query | Response | best2 |
|---|---|---|---|
| 7 | 12×10 | 1 | 10 |
| 8 | 12×18 | 1 | 18 |
| 9 | 12×19 | 0 | 18 |
Output is $12, 18$.
This trace shows how square probing locks onto the limiting dimension, and the second search extends the orthogonal side independently.
Second example: $a, b = 7, 7$.
| Step | Query | Response | best square |
|---|---|---|---|
| 1 | 10×10 | 0 | 1 |
| 2 | 5×5 | 1 | 5 |
| 3 | 7×7 | 1 | 7 |
| 4 | 8×8 | 0 | 7 |
Second phase:
| Step | Query | Response | best2 |
|---|---|---|---|
| 5 | 7×10 | 1 | 7 |
| 6 | 7×200 | 1 | 7 |
Output is $7, 7$, showing the symmetric case collapses cleanly without ambiguity.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(\log 200)$ | two binary searches over at most 200 values |
| Space | $O(1)$ | only a few integers for bounds |
The interaction limit of 20 queries is easily satisfied because each binary search uses at most 8 queries, giving a total well under the cap.
Test Cases
Since this is interactive, we simulate the judge with a helper.
import sys, io
def make_judge(a, b):
def run(inp: str) -> str:
it = iter(inp.strip().splitlines())
out = []
for line in it:
if line.startswith("?"):
_, c, d = line.split()
c, d = int(c), int(d)
ok = (c <= a and d <= b) or (c <= b and d <= a)
out.append("1" if ok else "0")
else:
break
return "\n".join(out)
return run
# mock solution using same logic but non-interactive
def solve_sim(a, b):
def ok(c, d):
return (c <= a and d <= b) or (c <= b and d <= a)
lo, hi = 1, 200
s = 1
while lo <= hi:
mid = (lo + hi) // 2
if ok(mid, mid):
s = mid
lo = mid + 1
else:
hi = mid - 1
lo, hi = 1, 200
t = 1
while lo <= hi:
mid = (lo + hi) // 2
if ok(s, mid):
t = mid
lo = mid + 1
else:
hi = mid - 1
return (s, t)
# provided samples
assert solve_sim(18, 12) == (12, 18)
# custom cases
assert solve_sim(1, 1) == (1, 1)
assert solve_sim(1, 100) == (1, 100)
assert solve_sim(7, 6) == (6, 7)
assert solve_sim(50, 50) == (50, 50)
| Test input | Expected output | What it validates |
|---|---|---|
| (18, 12) | (12, 18) | typical asymmetric case |
| (1, 1) | (1, 1) | minimum boundary |
| (1, 100) | (1, 100) | degenerate thin rectangle |
| (7, 6) | (6, 7) | swapped orientation |
| (50, 50) | (50, 50) | symmetric square |
Edge Cases
A key edge case is when one side is 1. In that situation, every square larger than 1 fails immediately, so the first binary search collapses quickly to 1. The second phase then directly recovers the other side without ambiguity because queries of the form $1 \times x$ behave exactly like a linear threshold test.
Another edge case is when $a = b$. Here every valid square up to $a$ succeeds in the first phase, so the algorithm cleanly returns $s = a$. The second phase then sees identical behavior for all $x \le a$, producing $x = a$ as well. The symmetry does not introduce any branching uncertainty because both dimensions coincide, so orientation never matters.