Gym Badges
This problem uses a technique known as an ’exchange argument’. This technique involves analyzing two cases of what happens when we challenge two gyms and : the first one being us challenging gym first and the second being us challenging gym first. We then use this to sort the gyms in such a way that for any two gyms and , if it is more optimal to challenge gym before gym , then gym comes before gym .
If we challenge gym before gym
Let our level before challenging either one of the gyms be . To fight gym , must be true (that is, our level must be less than the gym’s level cap). Challenging this gym would gain us levels, making our new level . Next, we challenge gym , necessitating . Our level after this becomes .
To be able to challenge both of these gyms, we need to satisfy two conditions simultaneously: and . The stricter (for example, if we had and , is the stricter condition) out of these two conditions will be the one with the smaller value, so we can combine both of these into:
If we challenge gym before gym
Mirroring the steps we’ve done above, we would obtain the condition for this being possible as:
Let’s say you had the choice between having to satisfy or to obtain gym badges (as in, ‘I will give you gym batches if your current level is or ). Which one would you choose? As you might have guessed, is the better choice here, since it works with more values than .
Conclusion: We would prefer to challenge gym before gym if:
(as this would give us more leeway in our choice for ).
Let us sort the gyms using this comparator:
typedef long long ll;
struct Gym {
ll L, X;
};
int main() {
ll n;
std::vector<Gym> a(n);
// ... take input
std::sort(a.begin(), a.end(), [](const Gym &i, const Gym &j) {
return std::min(i.L, j.L - i.X) > std::min(j.L, i.L - j.X);
});
}Note that this does not mean that we will necessarily challenge only a prefix of these gyms (it could very well be possible that our optimal solution excludes challenging some gym from the middle). Rather, our optimal solution will be a subsequence of , and we will challenge them in order from left to right.
The solution now is to greedily pick gyms. Let us maintain our current level () along with all gyms we have picked so far (initially empty). If , then we can challenge the gym. Otherwise, keeping in mind that each gym gives us exactly gym badge, it would perhaps be worth it to remove no more than gym from our list of challenged gyms if the gym we’re looking at increases our levels by less than the gym we’re removing.
Which gym would we remove? If , then removing the gym with the largest value would give us the highest chance of satisfying after the removal. Notice that this also gives us the highest chance of the gym we’re potentially adding increasing our levels less than the gym that we’re removing. So we do exactly that.
Note that this works only because we have ordered our gyms in such a way that we will always challenge them in the most optimal order. At the end, output the number of gyms that you have challenged, and you’re done!
The code for this is as follows. Note that I have used an std::multiset to store the gyms, but using an std::priority_queue would work too.
#include <bits/stdc++.h>
typedef long long ll;
struct Gym {
ll L, X;
};
bool operator<(const Gym &a, const Gym &b) {
if (a.X != b.X) {
return a.X < b.X;
}
return a.L < b.L;
}
int main() {
ll n;
std::cin >> n;
std::vector<Gym> a(n);
for (ll i = 0; i < n; ++ i) {
std::cin >> a[i].X;
}
for (ll i = 0; i < n; ++ i) {
std::cin >> a[i].L;
}
std::sort(a.begin(), a.end(), [](const Gym &i, const Gym &j) {
return std::min(i.L, j.L - i.X) > std::min(j.L, i.L - j.X);
});
std::multiset<Gym> dp;
ll level = 0;
for (ll i = 0; i < n; ++ i) {
if (dp.empty() || level <= a[i].L) {
dp.insert(a[i]);
level += a[i].X;
} else if (level - dp.rbegin()->X <= a[i].L && dp.rbegin()->X > a[i].X) {
level -= dp.rbegin()->X;
dp.erase(--dp.end());
dp.insert(a[i]);
level += a[i].X;
}
}
std::cout << dp.size() << "\n";
}