D - プリンターの割り当て / Printer Assignment Editorial by admin
Claude 4.6 Opus (Thinking)Overview
This problem asks for the earliest time (makespan) at which \(N\) printers can finish processing all \(M\) print jobs. It is solved by combining binary search on the answer with a greedy feasibility check.
Analysis
Key Observations
“Can all jobs be processed by time \(t\)?” has monotonicity
If processing is possible at time \(t\), then it is obviously also possible at any \(t' > t\). This monotonicity allows us to binary search on the answer.Given a time \(t\), the number of jobs each printer can handle is determined
Printer \(i\) can process \(\lfloor t / T_i \rfloor\) jobs by time \(t\).Jobs with larger page counts have fewer usable printers
By considering jobs in decreasing order of page count, a greedy approach that assigns jobs starting from those with the fewest available printers is effective.
Problems with the Naive Approach
Trying to explicitly assign jobs to printers leads to combinatorial explosion and TLE. The key is to reduce the problem to a “decision problem” via binary search and perform the check efficiently.
Correctness of the Check (Hall’s Theorem Perspective)
Sort both jobs and printers in decreasing order of capacity. The top \(k\) jobs with the largest page counts can only be processed by printers whose capacity is at least as large. Therefore, if for every \(k\), “the total processing capacity of printers that can handle the top \(k\) jobs \(\geq k\)” holds, then a valid assignment for all jobs exists (this corresponds to Hall’s Marriage Theorem).
Algorithm
- Sort printers in decreasing order of \(W_i\), and sort jobs in decreasing order of \(P_j\).
- If the largest job \(P_1\) cannot be processed by any printer, output \(-1\).
- Binary search on the answer. The check function
check(t)works as follows:- Maintain a pointer
ptrand the cumulative processing capacitysumof the printer group. - Iterate through jobs in decreasing order. For the \(k\)-th job:
- Sequentially include printers while
printers[ptr].first >= jobs[k], adding \(\lfloor t / T_{\text{ptr}} \rfloor\) tosum. - If
sum < k + 1, returnfalse(the top \(k+1\) jobs cannot all be processed).
- Sequentially include printers while
- If all jobs pass the check, return
true.
- Maintain a pointer
- Output the result of the binary search.
Concrete Example
Printers: \((W=10, T=2), (W=5, T=1)\); Jobs: \(P=8, P=4, P=3\)
For \(t=4\): - Printer 1 can process \(\lfloor 4/2 \rfloor = 2\) jobs - Printer 2 can process \(\lfloor 4/1 \rfloor = 4\) jobs - Jobs in decreasing order: \(8, 4, 3\) - \(k=0\): \(P=8\) → Only printer 1 can handle it → \(sum=2 \geq 1\) ✓ - \(k=1\): \(P=4\) → Printer 2 is also included → \(sum=2+4=6 \geq 2\) ✓ - \(k=2\): \(P=3\) → \(sum=6 \geq 3\) ✓ - Check result: feasible
Complexity
- Time complexity: \(O((N + M) \log(M \times \max(T_i)))\)
- Binary search runs \(O(\log(M \times 10^9)) \approx O(60)\) iterations
- Each check takes \(O(N + M)\)
- Space complexity: \(O(N + M)\)
Implementation Notes
Upper bound of binary search: The worst case is processing all jobs on a single printer, giving an upper bound of \(M \times 10^9\).
Overflow prevention: If
sumexceeds \(M\), clamp it to \(M\). Since \(\lfloor t / T_i \rfloor\) can become very large, uselong longand clamp appropriately.Pointer reuse: Since both jobs and printers are sorted, moving the pointer in one direction is sufficient to achieve the \(O(N + M)\) check.
Source Code
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int N, M;
cin >> N >> M;
vector<pair<long long, long long>> printers(N);
for (int i = 0; i < N; i++) {
cin >> printers[i].first >> printers[i].second;
}
vector<long long> jobs(M);
for (int j = 0; j < M; j++) {
cin >> jobs[j];
}
sort(printers.begin(), printers.end(), [](const auto& a, const auto& b) {
return a.first > b.first;
});
sort(jobs.begin(), jobs.end(), greater<long long>());
if (jobs[0] > printers[0].first) {
cout << -1 << endl;
return 0;
}
auto check = [&](long long t) -> bool {
long long sum = 0;
int ptr = 0;
for (int k = 0; k < M; k++) {
while (ptr < N && printers[ptr].first >= jobs[k]) {
sum += t / printers[ptr].second;
if (sum >= (long long)M) sum = (long long)M;
ptr++;
}
if (sum < (long long)(k + 1)) return false;
}
return true;
};
long long lo = 0, hi = (long long)M * 1000000000LL;
while (lo < hi) {
long long mid = (lo + hi) / 2;
if (check(mid)) {
hi = mid;
} else {
lo = mid + 1;
}
}
cout << lo << endl;
return 0;
}
This editorial was generated by claude4.6opus-thinking.
posted:
last update: