Throughout my grueling grind on Codeforces, I have stumbled across a few rather interesting problems, and the one mentioned in this article is no exception. The problem, 2164B: Even Modulo Pair features a trivial, yet ingenious solution that taps on one’s understanding in number theory and a little bit of math. When I first read the editorial along with multiple comments and videos, I was baffled as I could not understand the actual theoretical upper bound of the solution. After further reading and experimenting, I finally reached an understanding and I will try to explain my thought process and reasoning in this article.
Problem Statement
The problem statement is direct and straightforward where an important keyword, strictly increasing has been highlighted right off the bat. This is important as it suggests there will be no duplicates with the same value like so: , which is not the be confused with non-decreasing where the opposite applies. The problem requires the selection of two distinct elements and such that and .
To achieve this result, there can be only two possibilities: first, both and are even; second, both and are odd but if and only if . Hold on, I will prove how I derive at this solution later on.
Proof
If the provided array of size contains all even numbers, then the solution is trivial. One may simply perform a brute-force with doubly-nested for loops to check the numbers against one another, return when two even numbers are found and call it a day as . However, this solution has a quadratic time complexity of which will certainly lead to TLE as the time limit is 1 second and where which is operations per second of a typical CPU. As such, the algorithm will definitely take more than 1 second to complete…right?
Usually, yup, but with proper bounding, the algorithm can be proven to run within time constraint. Consider this, what if there is no even number in the array? Or, maybe there’s only 1 even number which is insufficient to form a pair. In this case, we’ll have to look for a valid pair of odd numbers. For example, let’s take a look at this sequence with only odd numbers with :
Let , now we need to find another odd number, such that . Notice that any odd number within the range of is valid? For example, , . Since , we can pick an odd number such that which will allow for . In order to satisfy the aforementioned criteria, the possible values for must only exist in the range of . Hence, if odd numbers exist within that range, one can simply pick any of the odd number as and return along with .
However, if there is no odd number in , this means that , so we have to find in the next range of . If no odd number exists within that range, then we will proceed with the next range of , so on and so forth and thus forming the sequence should no odd number exists within each range:
The above sequence shows the worst-case scenario with only odd numbers and no odd numbers exist between the valid range. As a result, each element is times of the first element, or 2 times the previous element where is the current index of the element in a 0-indexed array. Thus, in a worst-case scenario, will only be at maximum, 30 as any number afterward will surpass the input constraint of where represents each array element as evident in , which allows for an algorithm to be valid.
Solution in Code
To be honest, I had no clue this would get accepted as I only submitted when I really ran out of ideas. I was wary of the time constraint but after a lot of analysis, I still couldn’t think of a more optimized solution. In the end, it was more about proving why brute-forcing works rather than the implementation of some fancy algorithm. Anyway, here’s my solution in C++:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
ll n;
cin >> n;
vector<ll> nums(n);
for (auto &a : nums) {
cin >> a;
}
REP(i, 0, n) {
REP(j, i + 1, n) {
debug(nums[i], nums[j]);
if (nums[i] < nums[j]) {
if ((nums[j] % nums[i] == 0) || ((nums[j] % nums[i]) % 2 == 0)) {
cout << nums[i] << ' ' << nums[j] << '\n';
return;
}
}
}
}
cout << -1 << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
Conclusion
In conclusion, it is proven that the brute-force solution is bounded to which is more than enough to execute the algorithm within the 1 second time limit as required by the problem statement. The time complexity of the algorithm should be roughly where is the element with the largest value, or the last index. If , then , so for each number in array of size , we only need to check 30 times. Isn’t this a fascinating problem?