Submission #1325578

#TimeUsernameProblemLanguageResultExecution timeMemory
1325578kregsFestival (IOI25_festival)C++20
36 / 100
145 ms12128 KiB
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

// Standard types for calculation
typedef long long ll;

struct Coupon {
    int id;
    ll p;
    ll t;
};

// Robust Comparator for Multipliers
// Uses __int128 to strictly ensure no overflow errors during sorting
bool compareMultipliers(const Coupon& a, const Coupon& b) {
    // Priority: Free multipliers first
    if (a.p == 0 && b.p > 0) return true;
    if (b.p == 0 && a.p > 0) return false;
    
    // Efficiency: Minimize P*T / (T-1)
    unsigned __int128 val1 = (unsigned __int128)a.p * a.t * (b.t - 1);
    unsigned __int128 val2 = (unsigned __int128)b.p * b.t * (a.t - 1);
    
    if (val1 != val2) return val1 < val2;
    return a.p < b.p;
}

bool compareSpenders(const Coupon& a, const Coupon& b) {
    return a.p < b.p;
}

vector<int> max_coupons(int A, vector<int> P, vector<int> T) {
    int n = P.size();
    vector<Coupon> multipliers;
    vector<Coupon> spenders;

    ll total_spender_cost = 0;
    ll total_multiplier_cost = 0;

    // 1. Ingest and Separate
    for (int i = 0; i < n; ++i) {
        if (T[i] == 1) {
            spenders.push_back({i, (ll)P[i], (ll)T[i]});
            total_spender_cost += (ll)P[i];
        } else {
            multipliers.push_back({i, (ll)P[i], (ll)T[i]});
            total_multiplier_cost += (ll)P[i];
        }
    }

    // 2. Sort
    sort(multipliers.begin(), multipliers.end(), compareMultipliers);
    sort(spenders.begin(), spenders.end(), compareSpenders);

    // 3. Precompute Spender Prefix Sums (for fast binary search)
    int num_spenders = spenders.size();
    vector<ll> spender_prefix(num_spenders + 1, 0);
    for (int i = 0; i < num_spenders; ++i) {
        spender_prefix[i+1] = spender_prefix[i] + spenders[i].p;
    }

    // 4. Setup DP
    // We go up to 80 to be safe. 2^80 is effectively infinite.
    int m = multipliers.size();
    int limit_k = min(m, 80);
    
    // dp[k] = max tokens having picked exactly k multipliers
    vector<ll> dp(limit_k + 1, -1);
    
    // reconstruction[k] stores the list of multiplier INDICES used to reach state dp[k]
    // Since limit_k is small (80), storing a vector<int> for each state is fine (80 * 80 integers is tiny).
    // This removes all ambiguity about "which previous state did I come from?".
    vector<vector<int>> history(limit_k + 1);

    // Initial State
    dp[0] = A;
    
    // Global Best Tracker
    int max_coupons_count = -1;
    vector<int> best_multiplier_indices; // Stores the indices of multipliers in the best solution
    bool best_is_infinite = false;      // Flag if the best solution involves buying "all remaining"
    int best_split_index = -1;          // If infinite, at which index in 'multipliers' did we switch strategy?

    // Cap for "Infinite Money"
    ll INF_CAP = total_spender_cost + total_multiplier_cost + 20000;

    // Helper to update global best
    auto update_best_standard = [&](int k, ll current_money, const vector<int>& used_muls) {
        // How many spenders?
        auto it = upper_bound(spender_prefix.begin(), spender_prefix.end(), current_money);
        int cnt = (int)(it - spender_prefix.begin()) - 1;
        
        if (k + cnt > max_coupons_count) {
            max_coupons_count = k + cnt;
            best_multiplier_indices = used_muls;
            best_is_infinite = false;
        }
    };

    // Check initial state (0 multipliers)
    update_best_standard(0, A, {});

    // Track cost of multipliers processed so far
    ll current_mul_prefix_cost = 0;

    // 5. Main Loop
    for (int i = 0; i < m; ++i) {
        ll cost = multipliers[i].p;
        ll type = multipliers[i].t;
        int id = multipliers[i].id;

        // A. Check "Infinite Buy" Condition BEFORE processing this item
        // Can we take a known state dp[k], and from there buy ALL remaining multipliers (i...m-1) and ALL spenders?
        ll cost_remaining_muls = total_multiplier_cost - current_mul_prefix_cost;
        ll cost_everything = cost_remaining_muls + total_spender_cost;

        for (int k = 0; k <= limit_k; ++k) {
            if (dp[k] != -1 && dp[k] >= cost_everything) {
                int total = k + (m - i) + num_spenders;
                if (total > max_coupons_count) {
                    max_coupons_count = total;
                    best_multiplier_indices = history[k];
                    best_is_infinite = true;
                    best_split_index = i;
                }
            }
        }

        // B. Update DP Table (Standard Knapsack Step)
        // iterate backwards to avoid using same item twice for same depth
        for (int k = limit_k - 1; k >= 0; --k) {
            if (dp[k] != -1) {
                if (dp[k] >= cost) {
                    ll next_tokens = dp[k] - cost;
                    
                    // Safe Multiply
                    if (next_tokens > 0) {
                        if (INF_CAP / type < next_tokens) next_tokens = INF_CAP;
                        else next_tokens *= type;
                    }
                    
                    // If this is a better way to reach k+1 coupons
                    if (next_tokens > dp[k+1]) {
                        dp[k+1] = next_tokens;
                        // Copy history from k and add current id
                        history[k+1] = history[k];
                        history[k+1].push_back(id);
                    }
                }
            }
        }
        
        current_mul_prefix_cost += cost;

        // C. Check "Standard Best" Condition AFTER processing this item
        // For every reachable state, see if we should stop taking multipliers and just buy spenders.
        // (We only need to check the newly updated states, but checking all is safe and fast enough).
        for (int k = 1; k <= limit_k; ++k) {
            if (dp[k] != -1) {
                update_best_standard(k, dp[k], history[k]);
            }
        }
    }

    // 6. Final "Infinite" check after all multipliers processed
    // (Only relevant if we somehow have money left over after buying ALL multipliers sequentially)
    // This is covered by `best_split_index` logic or `update_best_standard` at the very end.

    // 7. Construct Final Result
    vector<int> result;

    if (best_is_infinite) {
        // 1. Add the base multipliers from DP
        result = best_multiplier_indices;
        
        // 2. Add ALL remaining multipliers starting from split_index
        for (int i = best_split_index; i < m; ++i) {
            // Check if this ID is already in result? 
            // No, best_multiplier_indices only contains items from 0 to split_index-1.
            // But we must be careful: 'history' stores IDs.
            // The loop for Infinite Check ran at start of step i.
            // So we take everything from index i onwards.
            result.push_back(multipliers[i].id);
        }
        
        // 3. Add ALL spenders
        for (const auto& s : spenders) {
            result.push_back(s.id);
        }
    } else {
        // Standard Solution
        // 1. Add multipliers
        result = best_multiplier_indices;
        
        // 2. Add Spenders
        // Re-calculate money to be absolutely precise
        ll final_money = (ll)A;
        
        // Use a map for O(1) property lookup during simulation
        // Or just scan (since result size is small for multipliers part)
        // Actually, we can just use the P/T values from original arrays if we trust IDs.
        
        // Map ID -> index in original P/T arrays
        // But we have P and T vectors passed in.
        for (int id : result) {
            ll p = P[id];
            ll t = T[id];
            if (final_money >= p) {
                final_money = (final_money - p) * t;
                if (final_money > INF_CAP) final_money = INF_CAP;
            }
        }
        
        auto it = upper_bound(spender_prefix.begin(), spender_prefix.end(), final_money);
        int count_spenders = (int)(it - spender_prefix.begin()) - 1;
        
        for (int i = 0; i < count_spenders; ++i) {
            result.push_back(spenders[i].id);
        }
    }

    return result;
}
#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...