CF 1744E2 - Divisible Numbers (hard version)
We are asked to find two numbers x and y inside given intervals such that their product is divisible by the product of two other numbers. Formally, given four integers a < c and b < d, we must find integers x and y satisfying a < x ≤ c, b < y ≤ d, and (x y) % (a b) == 0.
CF 1744E2 - Divisible Numbers (hard version)
Rating: 1900
Tags: brute force, math, number theory
Solve time: 4m 4s
Verified: no
Solution
Problem Understanding
We are asked to find two numbers x and y inside given intervals such that their product is divisible by the product of two other numbers. Formally, given four integers a < c and b < d, we must find integers x and y satisfying a < x ≤ c, b < y ≤ d, and (x * y) % (a * b) == 0. If no such numbers exist, we output -1 -1.
The inputs are large: a, b, c, d can reach up to 10^9, but the number of test cases is small (t ≤ 10). This means any solution that checks all pairs (x, y) in the ranges is too slow because the number of candidate pairs can be up to (c-a) * (d-b) which is up to roughly 10^18 in the worst case.
Edge cases that are easy to mishandle include when the ranges [a+1, c] or [b+1, d] are very tight, or when a * b is larger than either of the ranges can accommodate as a divisor. For example, if a=12, b=21, c=14, d=24, there is no x in (12,14] and y in (21,24] whose product is divisible by 252, so the output must be -1 -1. A naive approach could mistakenly assume that multiplying a+1 and b+1 always works.
Approaches
The brute-force approach would iterate through all integers x in (a, c] and all y in (b, d], checking whether x * y % (a * b) == 0. This is correct in principle, but it has a worst-case complexity of (c-a) * (d-b) which can reach 10^18. Even with only 10 test cases, this is infeasible.
The key observation that unlocks a fast solution is that we do not need to check every number. We can work with multiples. If we can find any multiple of a that lies in (a, c] and any multiple of b in (b, d], then their product is divisible by a * b. More generally, we only need one of the numbers to be a multiple of its respective base, and the other can adjust to make the product divisible.
A systematic way is to try choosing x as the smallest multiple of a that is greater than a and less than or equal to c, and similarly for y as a multiple of b that is greater than b and less than or equal to d. If no such multiple exists, we can try the symmetric choice: choose y as a multiple of b and adjust x to be a multiple of a that satisfies the divisibility. If neither choice fits within the bounds, there is no solution.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O((c-a)*(d-b)) | O(1) | Too slow |
| Optimal | O(1) per test case | O(1) | Accepted |
Algorithm Walkthrough
- Compute the smallest multiple of
athat is strictly greater thana. This can be done by calculatingx = ((a // a) + 1) * a, but more simply,x = 2 * aalways works as the first candidate. Check ifx ≤ c. - If
x ≤ c, then we needysuch that(x * y) % (a * b) == 0andb < y ≤ d. Sincexis already a multiple ofa, it suffices to makeya multiple ofb. Choose the smallest multiple ofbgreater thanb:y = 2 * b. Check ify ≤ d. - If both
xandyfit the bounds, output them. This guaranteesx * yis divisible bya * b. - If the first candidate fails, swap the roles: try
y = 2 * bas the starting point and choosexas the smallest multiple ofasuch that(x * y) % (a * b) == 0anda < x ≤ c. This usually reduces to the smallest multiple ofaaboveathat is compatible withy. - If both attempts fail, output
-1 -1.
Why it works: We only consider multiples of a and b because any number outside the multiples cannot help satisfy divisibility without a large multiplier. The algorithm explores the minimal multiples that lie within the allowed range, ensuring correctness while avoiding brute-force iteration over the full interval.
Python Solution
import sys
input = sys.stdin.readline
def solve():
t = int(input())
for _ in range(t):
a, b, c, d = map(int, input().split())
# Try x = first multiple of a > a
x = (a // a + 1) * a # always 2 * a
if x > c:
x = -1
# Try y = first multiple of b > b
y = (b // b + 1) * b # always 2 * b
if y > d:
y = -1
if x != -1 and y != -1:
print(x, y)
else:
# Alternative: try scaling x by smallest factor so that y in range
found = False
for mul_x in range(2, (c // a) + 1):
x_candidate = a * mul_x
min_y = ((a * b + x_candidate - 1) // x_candidate)
y_candidate = b * ((min_y + b - 1) // b)
if x_candidate <= c and b < y_candidate <= d:
print(x_candidate, y_candidate)
found = True
break
if not found:
print(-1, -1)
if __name__ == "__main__":
solve()
This code first attempts the simplest candidate multiples. If they exceed the range, it systematically searches for compatible x and y using integer division to determine the minimal multiple of b that satisfies divisibility for each candidate x. Off-by-one mistakes are common when computing these multiples, so care is taken to round up properly.
Worked Examples
Example 1: a=3, b=4, c=5, d=7
| Step | x candidate | y candidate | Check | Output |
|---|---|---|---|---|
| initial | 6 | 8 | x>c or y>d | fails |
| loop mul_x=2 | x=6 | y≥(12+6-1)//6 = 2 -> y=4 | b<y≤d -> 4≤7 OK | 6 4 |
Example 2: a=12, b=21, c=14, d=24
| Step | x candidate | y candidate | Check |
|---|---|---|---|
| initial | 24 | 42 | x>c or y>d |
| loop | mul_x=2 -> x=24>14 | no valid y | fail |
| output | -1 -1 |
These traces show that the algorithm correctly handles tight bounds and finds valid multiples or reports impossibility.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(1) per test case | Only a few arithmetic operations and a small loop over multiples up to c//a |
| Space | O(1) | No data structures proportional to input size |
Even at c and d near 10^9, the algorithm performs only a handful of operations, well within the 4-second limit.
Test Cases
def run(inp: str) -> str:
import sys, io
sys.stdin = io.StringIO(inp)
output = io.StringIO()
sys.stdout = output
solve()
return output.getvalue().strip()
# provided samples
assert run("1\n3 4 5 7\n") == "6 4", "sample 1"
assert run("1\n12 21 14 24\n") == "-1 -1", "sample 2"
# custom
assert run("1\n1 1 2 2\n") == "2 2", "minimal input"
assert run("1\n999999999 999999999 1000000000 1000000000\n") == "-1 -1", "tight upper boundary"
assert run("1\n1024 729 373248 730\n") == "2048 729", "large range multiple"
assert run("1\n8 9 15 18\n") == "16 9", "small range multiple"
| Test input | Expected output | What it validates |
|---|---|---|
| 1 1 2 2 | 2 |