Submission #1325589

#TimeUsernameProblemLanguageResultExecution timeMemory
1325589kregsFestival (IOI25_festival)C++20
55 / 100
1102 ms197188 KiB
#include <vector>
#include <algorithm>
#include <iostream>
#include <map>
#include <queue>

using namespace std;

// Use explicit long long for money to prevent overflow
typedef long long ll;
const ll MAX_VAL = 1e16; // Cap money at 10^16 (enough to buy everything)

// Structure to manage coupons in the sorting phase
struct Candidate {
    int type_idx; // 1, 2, or 3 representing Types 2, 3, 4
    int vec_idx;  // Index within the specific type vector
    ll price;
};

// Standard function signature required by the problem
std::vector<int> max_coupons(int A, std::vector<int> P, std::vector<int> T) {
    int n = P.size();

    // 1. Separate coupons into buckets based on Type
    // v[0] stores Type 1 (Spenders)
    // v[1] stores Type 2
    // v[2] stores Type 3
    // v[3] stores Type 4
    vector<vector<pair<ll, int>>> v(4);
    for (int i = 0; i < n; ++i) {
        if (T[i] >= 1 && T[i] <= 4) {
            v[T[i] - 1].push_back({(ll)P[i], i});
        }
    }

    // 2. Sort each bucket by Price (Cheapest first)
    for (int i = 0; i < 4; ++i) {
        sort(v[i].begin(), v[i].end());
    }

    // 3. Prepare "Profit Phase" Candidates (Types 2, 3, 4)
    vector<Candidate> candidates;
    for (int t = 1; t < 4; ++t) {
        for (int i = 0; i < v[t].size(); ++i) {
            candidates.push_back({t, i, v[t][i].first});
        }
    }

    // Sort candidates by Efficiency: P*T / (T-1)
    // We use cross-multiplication to compare: A < B
    // Formula: Pa * Ta * (Tb - 1) < Pb * Tb * (Ta - 1)
    sort(candidates.begin(), candidates.end(), [&](const Candidate& a, const Candidate& b) {
        ll Ta = a.type_idx + 1;
        ll Tb = b.type_idx + 1;
        // Use __int128 to ensure no overflow during comparison
        unsigned __int128 val1 = (unsigned __int128)a.price * Ta * (Tb - 1);
        unsigned __int128 val2 = (unsigned __int128)b.price * Tb * (Ta - 1);
        return val1 < val2;
    });

    // 4. Execute "Profit Phase" (Greedy)
    // Buy any coupon that results in strictly MORE money than before.
    vector<int> result_indices;
    vector<int> counters(4, 0); // Tracks how many of each type we have used
    ll current_money = A;

    for (const auto& c : candidates) {
        int t = c.type_idx;
        int idx = c.vec_idx;

        // We can only process this candidate if it is the NEXT available one in its bucket.
        // Due to efficiency sorting, valid candidates usually appear in order, 
        // but we must skip "future" expensive ones if we haven't bought cheaper ones yet.
        if (idx == counters[t]) {
            ll price = v[t][idx].first;
            
            if (current_money < price) {
                // Cannot afford this specific coupon.
                // Since candidates are sorted by efficiency (easier affordability),
                // we likely can't afford subsequent ones either, but we continue to be safe.
                continue; 
            }

            ll mult = t + 1;
            ll next_val = (current_money - price) * mult;

            // Cap the value
            if (next_val > MAX_VAL) next_val = MAX_VAL;

            if (next_val > current_money) {
                current_money = next_val;
                result_indices.push_back(v[t][idx].second);
                counters[t]++;
            } else {
                // If the most efficient available coupon is not profitable,
                // no other coupon will be. We stop the Profit Phase.
                break;
            }
        }
    }

    // 5. BFS Phase (State Space Search)
    // We explore buying "Costly" coupons (where Money decreases or stays same).
    // State: {count_type2, count_type3, count_type4} (Offsets from 'counters')
    
    // Base offsets from profit phase
    int base_c1 = counters[1];
    int base_c2 = counters[2];
    int base_c3 = counters[3];

    // DP Map: State Vector -> Max Money
    map<vector<int>, ll> dp;
    
    // Parent Map for reconstruction: State Vector -> {TypeAdded, IndexInResultVector}
    // TypeAdded: 1 for T2, 2 for T3, 3 for T4
    map<vector<int>, pair<int, int>> parent;

    vector<int> start_state = {0, 0, 0};
    dp[start_state] = current_money;
    
    queue<vector<int>> q;
    q.push(start_state);

    // Track the global maximum coupons found
    int max_coupons_found = 0;
    vector<int> best_state_vector = start_state;

    // Precompute Spender (Type 1) prefix sums for O(log N) lookup
    vector<ll> type1_prefix;
    type1_prefix.push_back(0);
    for (const auto& p : v[0]) {
        type1_prefix.push_back(type1_prefix.back() + p.first);
    }

    // Helper to check total coupons (Multipliers + Spenders)
    auto check_solution = [&](ll money, int extra_mults, const vector<int>& state) {
        auto it = upper_bound(type1_prefix.begin(), type1_prefix.end(), money);
        int spenders_count = (int)(it - type1_prefix.begin()) - 1;
        
        int total = (int)result_indices.size() + extra_mults + spenders_count;
        if (total > max_coupons_found) {
            max_coupons_found = total;
            best_state_vector = state;
        }
    };

    // Initial check
    check_solution(current_money, 0, start_state);

    while (!q.empty()) {
        vector<int> s = q.front();
        q.pop();

        ll cur_m = dp[s];
        int u1 = s[0];
        int u2 = s[1];
        int u3 = s[2];
        int total_extra = u1 + u2 + u3;

        // Try adding Type 2 (Index 1 in v)
        if (base_c1 + u1 < v[1].size()) {
            ll p = v[1][base_c1 + u1].first;
            if (cur_m >= p) {
                ll next_m = (cur_m - p) * 2;
                if (next_m > MAX_VAL) next_m = MAX_VAL;

                vector<int> next_s = {u1 + 1, u2, u3};
                if (dp.find(next_s) == dp.end() || dp[next_s] < next_m) {
                    dp[next_s] = next_m;
                    parent[next_s] = {1, v[1][base_c1 + u1].second};
                    q.push(next_s);
                    check_solution(next_m, total_extra + 1, next_s);
                }
            }
        }

        // Try adding Type 3 (Index 2 in v)
        if (base_c2 + u2 < v[2].size()) {
            ll p = v[2][base_c2 + u2].first;
            if (cur_m >= p) {
                ll next_m = (cur_m - p) * 3;
                if (next_m > MAX_VAL) next_m = MAX_VAL;

                vector<int> next_s = {u1, u2 + 1, u3};
                if (dp.find(next_s) == dp.end() || dp[next_s] < next_m) {
                    dp[next_s] = next_m;
                    parent[next_s] = {2, v[2][base_c2 + u2].second};
                    q.push(next_s);
                    check_solution(next_m, total_extra + 1, next_s);
                }
            }
        }

        // Try adding Type 4 (Index 3 in v)
        if (base_c3 + u3 < v[3].size()) {
            ll p = v[3][base_c3 + u3].first;
            if (cur_m >= p) {
                ll next_m = (cur_m - p) * 4;
                if (next_m > MAX_VAL) next_m = MAX_VAL;

                vector<int> next_s = {u1, u2, u3 + 1};
                if (dp.find(next_s) == dp.end() || dp[next_s] < next_m) {
                    dp[next_s] = next_m;
                    parent[next_s] = {3, v[3][base_c3 + u3].second};
                    q.push(next_s);
                    check_solution(next_m, total_extra + 1, next_s);
                }
            }
        }
    }

    // 6. Reconstruction
    // Retrieve the BFS multipliers by backtracking from best_state_vector
    vector<int> bfs_indices;
    vector<int> curr = best_state_vector;

    while (curr[0] > 0 || curr[1] > 0 || curr[2] > 0) {
        // Safety check to prevent RTE if state is missing (should not happen)
        if (parent.find(curr) == parent.end()) break;

        pair<int, int> info = parent[curr];
        bfs_indices.push_back(info.second);
        
        // Backtrack state: info.first is 1, 2, or 3 corresponding to index 0, 1, 2
        curr[info.first - 1]--;
    }

    // BFS reconstruction is in reverse order (last bought first)
    reverse(bfs_indices.begin(), bfs_indices.end());

    // Append BFS results to the Profit Phase results
    result_indices.insert(result_indices.end(), bfs_indices.begin(), bfs_indices.end());

    // 7. Add Spenders
    // Calculate final money based on the optimal DP state
    ll final_money = dp[best_state_vector];
    
    auto it = upper_bound(type1_prefix.begin(), type1_prefix.end(), final_money);
    int cnt_spenders = (int)(it - type1_prefix.begin()) - 1;
    
    for (int i = 0; i < cnt_spenders; ++i) {
        result_indices.push_back(v[0][i].second);
    }

    return result_indices;
}
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...