Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
For a fixed pair , don't think about the whole arrays yet. Ask: when can some produce exactly quotient and remainder ?
If and , then necessarily and .
Since all , the smallest possible valid divisor is . So a pair works iff even this cheapest choice keeps .
That gives the condition , or equivalently . Each can match any remainder up to some maximum value.
Now it is just greedy matching: convert every into its maximum allowed remainder, sort these limits, sort , and match the smallest remaining remainder to the smallest limit that can handle it.
Let a quotient value be and a remainder value be . We need to know whether they can be paired by some operation.
The operation asks for integers such that:
By quotient-remainder decomposition, this means
with
Here every is positive, so . That already gives ? Nope, careful: it gives . The smallest divisor that could possibly work is therefore
For fixed and , increasing only increases , because . So if the smallest valid does not fit under , nothing else will. The pair works exactly when
Expand it:
So for a fixed quotient , the largest remainder it can accept is
If some remainder , then choose and . Done. If , no valid exists. That's the whole math part. The rest is matching without doing something hilariously overkill like full bipartite matching.
Turning the problem into matching
Each quotient value becomes one limit:
That quotient can match any remainder with
So every quotient has a simple prefix of acceptable remainders: all small enough values. This structure is greedy-friendly.
Sort all limits in increasing order. Sort all remainders in increasing order.
Then scan the limits from smallest to largest, keeping a pointer to the smallest unused remainder.
For the current limit :
This is not vibes-based greedy. It is forced.
If the smallest available remainder does not fit into the current smallest limit, then no available remainder fits that limit. So skipping that quotient loses nothing.
If it does fit, matching it is always safe: using the smallest remainder is the least demanding choice, leaving larger remainders for larger future limits. Giving this easy remainder to a later, more flexible quotient would be pointless nonsense.
Algorithm
For each test case:
Correctness proof
We prove the algorithm outputs the maximum number of operations.
First, for any quotient and remainder , they can be used in one operation iff .
If an operation exists, then and . Since , we have
Because , it follows that , which is equivalent to .
Conversely, if , choose and . Then , , and , so the quotient is and the remainder is . Thus the pair is valid.
So the original problem is exactly: match each limit with at most one remainder such that .
Now sort limits and remainders increasingly. Consider the greedy scan.
When processing a limit , let be the smallest unused remainder.
If , then every unused remainder is also greater than , so this limit cannot be matched in any valid solution using the remaining remainders. Skipping it is forced.
If , greedy matches them. Take any optimal solution for the remaining items plus this limit. If this optimal solution does not match to , then either is unused, or is matched to some later limit . In both cases, we can match to instead. This does not reduce the number of matches, because fits , and later limits are at least as flexible as . Therefore there exists an optimal solution that includes the greedy match.
By repeating this argument for every processed limit, every greedy decision is compatible with some optimal solution. Hence the greedy algorithm is optimal.
Complexity
Sorting dominates:
per test case, with total , so this easily fits. Memory usage is .
Use long long, because is up to . The products are still safe in signed 64-bit here, but computing the limit directly as is cleaner anyway.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
ll k;
cin >> n >> k;
vector<ll> lim(n), r(n);
for (int i = 0; i < n; i++) {
ll q;
cin >> q;
lim[i] = (k - q) / (q + 1);
}
for (int i = 0; i < n; i++) cin >> r[i];
sort(lim.begin(), lim.end());
sort(r.begin(), r.end());
int ptr = 0, ans = 0;
for (ll x : lim) {
if (ptr < n && r[ptr] <= x) {
ans++;
ptr++;
}
}
cout << ans << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
ll k;
cin >> n >> k;
vector<ll> lim(n), r(n);
for (int i = 0; i < n; i++) {
ll q;
cin >> q;
lim[i] = (k - q) / (q + 1);
}
for (int i = 0; i < n; i++) cin >> r[i];
sort(lim.begin(), lim.end());
sort(r.begin(), r.end());
int ptr = 0, ans = 0;
for (ll x : lim) {
if (ptr < n && r[ptr] <= x) {
ans++;
ptr++;
}
}
cout << ans << '\n';
}
}