Greedy Algorithms Reference

Mastering the Greedy strategy for Competitive Programming. These problems are selected from Codeforces to help you build intuition from level 800 to 1600.

Level 800: Mahmoud and Ehab

Easy

Problem: 959A - Even-Odd Game

Hint 1
Mahmoud starts first. He wants to pick an even number $a$ such that $n-a$ is still playable or he wins.
Hint 2
Ehab can only pick odd numbers. If $n$ is even, Mahmoud can pick $a=n$ immediately.
Hint 3
What happens if $n$ is odd? Can Mahmoud pick any even number that leaves Ehab with no moves?

Detailed Solution

The game logic is simple: Mahmoud wins if $n$ is even, and Ehab wins if $n$ is odd. Why? If $n$ is even, Mahmoud can choose $a = n$ (since $n$ is even and $1 \le a \le n$). After this move, $n$ becomes $0$, and Ehab cannot make any move because there's no odd number $a$ such that $1 \le a \le 0$. If $n$ is odd, any even number Mahmoud picks will leave an odd number for Ehab. Ehab can then pick that entire odd number to win.

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    // If n is even, Mahmoud wins by taking the whole number
    if (n % 2 == 0) {
        cout << "Mahmoud" << endl;
    } else {
        cout << "Ehab" << endl;
    }
    return 0;
}

Level 1000: Dragons

Medium-Easy

Problem: 230A - Dragons

Hint 1
Does the order in which you fight the dragons matter?
Hint 2
To maximize your strength, you should fight dragons that you can *already* defeat. Which ones should you pick first?
Hint 3
Sort the dragons by their strength in ascending order. If you can't beat the weakest available dragon, you can't beat any.

Detailed Solution

This is a classic Sorting Greedy problem. To win, you need to gain as much strength as possible. The only way to gain strength is by defeating a dragon. If you have a choice between two dragons, you should always pick the one with the lower strength first, provided your current strength is greater than its strength. By defeating weaker dragons first, your strength increases, making it easier to defeat stronger ones later.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int s, n;
    cin >> s >> n;
    vector<pair<int, int>> dragons(n);
    for (int i = 0; i < n; ++i) {
        cin >> dragons[i].first >> dragons[i].second;
    }
    
    // Greedy Strategy: Sort by dragon strength (x_i)
    sort(dragons.begin(), dragons.end());
    
    bool possible = true;
    for (int i = 0; i < n; ++i) {
        if (s > dragons[i].first) {
            s += dragons[i].second;
        } else {
            possible = false;
            break;
        }
    }
    
    if (possible) cout << "YES" << endl;
    else cout << "NO" << endl;
    
    return 0;
}

Level 1200: Taxi

Medium

Problem: 158B - Taxi

Hint 1
Each taxi can carry at most 4 people. Groups cannot be split.
Hint 2
Prioritize groups of 4. Then, combine groups of 3 with groups of 1. What's left?
Hint 3
Combine groups of 2 with other groups of 2. Finally, fit any remaining groups of 1 into any available space.

Detailed Solution

The greedy strategy here is to fill each taxi as much as possible. 1. Groups of 4 always need their own taxi. 2. Groups of 3 should be paired with a group of 1 if available. 3. Two groups of 2 can share a taxi. 4. If one group of 2 is left, it can be paired with up to two groups of 1. 5. Any remaining groups of 1 fill taxis in sets of 4.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, x;
    cin >> n;
    vector<int> count(5, 0);
    for (int i = 0; i < n; ++i) {
        cin >> x;
        count[x]++;
    }
    
    int taxis = count[4];
    
    // Pair 3s with 1s
    taxis += count[3];
    count[1] = max(0, count[1] - count[3]);
    
    // Pair 2s with 2s
    taxis += count[2] / 2;
    if (count[2] % 2 == 1) {
        taxis++;
        count[1] = max(0, count[1] - 2);
    }
    
    // Remaining 1s
    taxis += (count[1] + 3) / 4;
    
    cout << taxis << endl;
    return 0;
}

Level 1400: Woodcutters

Medium-Hard

Problem: 545C - Woodcutters

Hint 1
The first tree can always fall to the left, and the last tree can always fall to the right.
Hint 2
For any tree in between, try falling it to the left first. If it doesn't fit, try falling it to the right.
Hint 3
Falling a tree to the left only depends on the previous tree's final position. Falling it to the right only affects the next tree's possibility to fall left.

Detailed Solution

The greedy approach works because falling a tree to the left is always better than falling it to the right, and falling it to the right is better than not falling it at all. Why? If a tree can fall left, it doesn't occupy any space to its right, leaving more room for future trees. If it can't fall left but can fall right, we take it because it's a guaranteed point, and it only potentially blocks the immediate next tree.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;
    if (n == 1) { cout << 1 << endl; return 0; }
    
    vector<long long> x(n), h(n);
    for (int i = 0; i < n; ++i) cin >> x[i] >> h[i];
    
    int count = 2; // First and last always fall
    long long last_pos = x[0];
    
    for (int i = 1; i < n - 1; ++i) {
        if (x[i] - h[i] > last_pos) {
            count++;
            last_pos = x[i];
        } else if (x[i] + h[i] < x[i+1]) {
            count++;
            last_pos = x[i] + h[i];
        } else {
            last_pos = x[i];
        }
    }
    
    cout << count << endl;
    return 0;
}

Level 1600: Set or Decrease

Hard

Problem: 1622C - Set or Decrease

Hint 1
You can decrease the minimum element $a_1$ and then set other large elements to this new $a_1$.
Hint 2
If you decide to "set" $k$ elements, which ones should they be? The largest ones, obviously.
Hint 3
For a fixed number of "set" operations $k$, you can calculate the required number of "decrease" operations using a simple formula or binary search.

Detailed Solution

The strategy is to sort the array and always use $a_1$ (the minimum) to replace the largest elements. Let $k$ be the number of elements we replace ($0 \le k \le n-1$). If we replace $k$ elements, we replace $a_n, a_{n-1}, \dots, a_{n-k+1}$ with $a_1 - x$, where $x$ is the number of times we decreased $a_1$. The total sum becomes: $(a_1 - x) \cdot (k + 1) + \sum_{i=2}^{n-k} a_i \le k$. We iterate through all possible values of $k$ and find the minimum $x+k$.

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

void solve() {
    long long n, k;
    cin >> n >> k;
    vector<long long> a(n);
    for (int i = 0; i < n; ++i) cin >> a[i];
    sort(a.begin(), a.end());
    
    vector<long long> pref(n + 1, 0);
    for (int i = 0; i < n; ++i) pref[i+1] = pref[i] + a[i];
    
    long long ans = 2e18;
    for (int y = 0; y < n; ++y) {
        // y is number of elements to set to a[0]-x
        long long current_sum = pref[n-y] + y * a[0];
        long long diff = current_sum - k;
        long long x = 0;
        if (diff > 0) {
            x = (diff + y) / (y + 1);
        }
        ans = min(ans, x + y);
    }
    cout << ans << endl;
}

int main() {
    int t; cin >> t;
    while (t--) solve();
    return 0;
}