Parchment
Editorial written by Ekansh Majumder.
Binary search is a prerequisite for subtask 6 and above.
The solution to this problem was also discussed here on the SPOI YouTube channel.
Subtask #1
In this subtask, . All the numbers are in the range , which means every element in the array is .
A single segment of length (which covers ) can cover all numbers in just one operation. Since for all queries, the answer is simply for every query.
for (int i = 0; i < q; i++) {
cout << 0 << "\n";
}The time complexity of this solution is since we iterate through the queries and output for each query in time.
Subtask #2
In this subtask, . You are allowed to use at most 1 segment to cover the entire array.
To cover all points using a single interval , the interval must stretch from the minimum element to the maximum element:
The answer is the difference between the largest and the smallest element in the input array.
For example, given the array and ; to cover the entire array in one operation, we need to cover the span . The difference between the maximum and the minimum element is , so we delete .
int mini = P[0], maxi = P[0];
for (int i = 1; i < N; i++) {
mini = min(mini, P[i]);
maxi = max(maxi, P[i]);
}
int ans = maxi - mini;The time complexity of this solution is .
Subtask #3
Here, .
We can divide this subtask into two cases. For , we use the same solution as subtask 2. We now focus on the case where .
First, sort the array in non-decreasing order: .
Notice that intervals are continuous, so one segment must start at index and cover a prefix of the array up to some index , while the other segment covers the remaining suffix from index to the maximum element at index .
For any chosen split point :
- The first segment covers , requiring a length of at least .
- The second segment covers , requiring a length of at least .
Since both segments use the same power level , the device requires:
To find the minimum power level, iterate through all () and take the minimum of these values.
sort(P.begin(), P.end());
int ans = P[N - 1] - P[0];
for (int i = 0; i < N - 1; i++) {
int left = P[i] - P[0];
int right = P[N - 1] - P[i + 1];
int req = max(left, right);
ans = min(ans, req);
}The time complexity of this solution is , with the log factor incurred due to having to sort the array.
Subtask #4
Every element is in the range . We divide the problem into three cases:
- Case 1: The array contains only s. The answer is simply (refer to subtask 1).
- Case 2: The array contains only s. The answer is simply (refer to subtask 1).
- Case 3: The array contains both s and s:
- If : We can only use segment. It must cover both and , so .
- If : We can use separate segments of length (one on and one on ). Thus, the answer is .
bool has_1 = false, has_2 = false;
for (int i = 0; i < N; i++) {
if (P[i] == 1) has_1 = true;
if (P[i] == 2) has_2 = true;
}
for (int t = 0; t < q; t++) {
int f;
cin >> f;
if (has_1 && has_2 && f == 1) {
cout << 1 << "\n";
} else {
cout << 0 << "\n";
}
}The time complexity of this solution is .
Subtask #5
(Reading this subtask is optional but recommended.)
and
Notice that the answer will always be in the range . Anything bigger is not required since clears the whole range. Keep this property in mind, as we will use it for later subtasks and the full solution.
Iterate linearly from to . For each candidate , greedily count how many intervals of length are needed to cover the entire sorted array.
The very first that is acceptable (requires segments) is guaranteed to be the minimum valid , as every smaller value failed. We can stop and output it as the answer.
For a fixed , run an outer loop over the array, increment the segment count, and use an inner loop to advance past all elements covered within .
int getCount(vector<int> P, int K) {
int count = 0;
int i = 0;
int n = P.size();
while (i < n) {
count++;
int last = P[i] + K;
while (i < n && P[i] <= last) {
i++;
}
}
return count;
}Query loop:
for (int k = 0; k <= A; k++) {
if (getCount(P, k) <= F) { // P is the input array
cout << k << "\n";
break;
}
}The time complexity of this solution is :
- Sorting the array takes .
- For each of the queries, we test values of from up to (at most candidates).
- Checking each candidate takes time.
Subtask #6
Note: This approach also passes subtasks 5, 6, 7, and 8. Combined with the first four subtasks, this scores 75/100 points. We will skip directly to the full solve after this subtask.
(Reading subtask 5 is recommended, as this is an optimization of that method.)
In subtask 5, we established that the answer will always be in the range .
- If a length can cover all points in operations, then any can also do it.
- If a length cannot cover all points in operations, then any can also not do it.
The validity of is monotonic:
This means we do not need to linearly search for . We can binary search on the answer to find the minimum value that satisfies the condition. Our verification function is identical to the greedy check in Subtask 5.
#include <bits/stdc++.h>
using namespace std;
// Checking if the given K is valid
bool can(int K, int F, vector<int> &nums) {
int n = nums.size();
int uses = 0;
int i = 0;
while (i < n) {
uses++;
int end = nums[i] + K;
while (i < n && nums[i] <= end) i++;
if (uses > F) return false; // Early exit
}
return uses <= F;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, A;
cin >> n >> A;
vector<int> nums(n);
for (int i = 0; i < n; i++) cin >> nums[i];
sort(nums.begin(), nums.end());
int q;
cin >> q;
vector<int> F(q);
for (int i = 0; i < q; i++) cin >> F[i];
for (int f : F) {
int low = 0, high = A, ans = A;
while (low <= high) {
int mid = (low + high) / 2;
if (can(mid, f, nums)) {
ans = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
cout << ans << "\n";
}
}The time complexity of this solution is :
- Sorting the array initially takes .
- For each query, binary search on runs for iterations.
- In each binary search step, the greedy verification function runs in time.
Full solution
(Reading subtask 6 is recommended before this section.)
The solution precomputes the minimum number of operations required for every possible and stores it in an array cache, where cache[k] represents the minimum operations needed for power level .
Notice that cache is sorted in non-increasing order: as increases, the number of required operations decreases.
Each query can then be answered directly by binary searching on cache.
Instead of manually stepping element-by-element inside the simulation loop, we use upper_bound to jump directly to the first element strictly greater than .
#include <bits/stdc++.h>
using namespace std;
#define int long long
vector<int> cache; // cache[k] is the min number of operations required with K = k
void cacheFill(int &k, vector<int> &a) {
// Filling up the cache array
cache.resize(k);
int n = a.size();
for (int currk = 0; currk < k; currk++) {
int i = 0;
int uses = 0;
while (i < n) {
uses++;
int end = a[i] + currk;
i = upper_bound(a.begin() + i, a.end(), end) - a.begin();
}
cache[currk] = uses;
}
}
int32_t main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, a;
cin >> n >> a;
vector<int> arr(n);
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
sort(arr.begin(), arr.end());
int limits = a + 1;
cacheFill(limits, arr);
int q;
cin >> q;
while (q--) {
int f;
cin >> f;
int low = 0;
int high = limits - 1;
int ans = limits;
while (low <= high) {
int mid = (low + high) / 2;
if (cache[mid] <= f) {
ans = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
cout << ans << '\n';
}
return 0;
}The time complexity of this solution is :
- Sorting the input array takes .
- In the precomputation step, for a fixed interval length , the simulation advances by at least in value each jump, resulting in at most jumps.
- Summing over all , the total number of jumps is bounded by the Harmonic series:
- Each jump uses
upper_bound, costing , giving a precomputation time of (around operations). - Each of the queries is answered via binary search over
cacheof size in time.