Submission #1351980

#TimeUsernameProblemLanguageResultExecution timeMemory
1351980MDSProUplifting Excursion (BOI22_vault)C++20
100 / 100
1436 ms15148 KiB
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

/*
 * BOI 2022 - Uplifting Excursion (Vault)
 * 
 * Key idea:
 * 1. Greedy: take as many pieces as possible without "overshooting" L
 * 2. Prove that optimal differs from greedy by at most 2M add/remove operations
 * 3. Use DP over small range to find the best adjustment
 */

const ll NEG_INF = -1e18;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int M;
    ll L;
    cin >> M >> L;
    
    vector<ll> A(2 * M + 1);  // A[i] = count of pieces with uplift (i - M)
    ll totalUplift = 0;       // uplift if we take everything
    ll totalCount = 0;        // count if we take everything
    
    for (int i = 0; i <= 2 * M; i++) {
        cin >> A[i];
        int uplift = i - M;  // actual uplift value
        totalUplift += A[i] * uplift;
        totalCount += A[i];
    }
    
    // Handle uplift 0 separately: always take all of them (they're free!)
    ll zeroCount = A[M];
    A[M] = 0;
    
    // Greedy solution:
    // If totalUplift >= L: take all negatives, prefix of positives
    // If totalUplift < L: take all positives, prefix of negatives (by absolute value)
    
    vector<ll> greedy(2 * M + 1, 0);  // greedy[i] = how many pieces of uplift (i-M) we take
    ll greedyUplift = 0;
    ll greedyCount = zeroCount;  // always take all zeros
    
    if (totalUplift >= L) {
        // Take all negative uplift pieces
        for (int i = 0; i < M; i++) {  // uplift = i - M < 0
            greedy[i] = A[i];
            greedyUplift += A[i] * (i - M);
            greedyCount += A[i];
        }
        // Take prefix of positive uplift pieces (smallest first)
        for (int i = M + 1; i <= 2 * M; i++) {  // uplift = i - M > 0
            int uplift = i - M;
            // How many can we take without exceeding L?
            if (greedyUplift >= L) break;
            ll canTake = min(A[i], (L - greedyUplift - 1) / uplift + 1);
            // Actually we want greedyUplift + canTake * uplift <= L... but we want max canTake
            // such that greedyUplift + canTake * uplift <= L
            // Wait, we want to NOT exceed L, so:
            // greedyUplift + canTake * uplift <= L
            // canTake <= (L - greedyUplift) / uplift
            // But we also want to be as close to L as possible
            
            // Let's just take as many as possible without going over L
            ll maxTake = A[i];
            if (greedyUplift + maxTake * uplift <= L) {
                greedy[i] = maxTake;
                greedyUplift += maxTake * uplift;
                greedyCount += maxTake;
            } else {
                // Take floor((L - greedyUplift) / uplift) pieces
                ll take = (L - greedyUplift) / uplift;
                greedy[i] = take;
                greedyUplift += take * uplift;
                greedyCount += take;
            }
        }
    } else {
        // Take all positive uplift pieces
        for (int i = M + 1; i <= 2 * M; i++) {  // uplift = i - M > 0
            greedy[i] = A[i];
            greedyUplift += A[i] * (i - M);
            greedyCount += A[i];
        }
        // Take prefix of negative uplift pieces (closest to 0 first, i.e., -1, -2, -3, ...)
        for (int i = M - 1; i >= 0; i--) {  // uplift = i - M < 0
            int uplift = i - M;  // negative
            if (greedyUplift <= L) break;
            ll canTake = A[i];
            // We want greedyUplift + canTake * uplift >= L
            // Since uplift < 0, adding pieces decreases greedyUplift
            // We want to stay >= L
            if (greedyUplift + canTake * uplift >= L) {
                greedy[i] = canTake;
                greedyUplift += canTake * uplift;
                greedyCount += canTake;
            } else {
                // Take floor((greedyUplift - L) / (-uplift)) pieces
                ll take = (greedyUplift - L) / (-uplift);
                greedy[i] = take;
                greedyUplift += take * uplift;
                greedyCount += take;
            }
        }
    }
    
    // Now greedyUplift is in [L - M, L] or [L, L + M]
    // We need to adjust by delta = L - greedyUplift
    // The adjustment uses at most 2M operations total
    
    ll delta = L - greedyUplift;
    
    // DP: dp[d] = max net change in count to achieve uplift change of d
    // d ranges from -2*M*M to +2*M*M theoretically, but we only need around delta
    // Let's use offset: d + offset -> index
    // We need d in range roughly [-2*M*M, 2*M*M] but actually much smaller
    // Since we do at most 2M ops, each changing uplift by at most M, 
    // total change is at most 2*M*M
    
    // But actually we only care about d = delta, which is in [-M, M]
    // However during DP construction we might hit values in [-2*M*M, 2*M*M]
    
    int maxDelta = 2 * M * M + M;  // safe upper bound
    int offset = maxDelta;
    int dpSize = 2 * maxDelta + 1;
    
    vector<ll> dp(dpSize, NEG_INF);
    dp[offset] = 0;  // 0 change in uplift, 0 change in count
    
    // For each uplift value (except 0), update DP using sliding window deque
    // This is a bounded knapsack: we can take between -canRemove and +canAdd items of each type
    
    for (int i = 0; i <= 2 * M; i++) {
        if (i == M) continue;  // skip uplift 0
        
        int uplift = i - M;
        int step = abs(uplift);
        
        // How many can we ADD? (pieces not in greedy, but available)
        ll addLim = max(0LL, min((ll)M, A[i] - greedy[i]));
        // How many can we REMOVE? (pieces in greedy)
        ll remLim = max(0LL, min((ll)M, greedy[i]));
        
        if (addLim == 0 && remLim == 0) continue;
        
        // We want: dp_new[j] = max over t in [-remLim, addLim] of: dp[j - t*uplift] + t
        //
        // For each residue class r mod |uplift|, process separately.
        // Within a class, let pos[k] = r + k*step be the k-th index.
        // 
        // For uplift > 0: j - t*uplift = pos[k] - t*step = pos[k - t]
        //   So we want max of dp[pos[k']] + (k - k') for k' in [k - addLim, k + remLim]
        //   = k + max of (dp[pos[k']] - k') for k' in [k - addLim, k + remLim]
        //
        // For uplift < 0: j - t*uplift = pos[k] + t*step = pos[k + t]
        //   So we want max of dp[pos[k']] + (k' - k) for k' in [k - remLim, k + addLim]
        //   = -k + max of (dp[pos[k']] + k') for k' in [k - remLim, k + addLim]
        
        vector<ll> newDp = dp;
        
        for (int r = 0; r < step; r++) {
            // Collect indices in this residue class
            vector<int> pos;
            for (int j = r; j < dpSize; j += step) {
                pos.push_back(j);
            }
            int sz = pos.size();
            if (sz == 0) continue;
            
            // Prepare values array
            vector<ll> val(sz);
            for (int k = 0; k < sz; k++) {
                val[k] = dp[pos[k]];
            }
            
            if (uplift > 0) {
                // Window is [k - addLim, k + remLim]
                // We want max of (val[k'] - k') over this window, then add k
                
                // Transform: store val[k'] - k' in arr
                vector<ll> arr(sz);
                for (int k = 0; k < sz; k++) {
                    arr[k] = (val[k] == NEG_INF) ? NEG_INF : val[k] - k;
                }
                
                // Sliding window maximum with window [k - addLim, k + remLim]
                // Window size = addLim + remLim + 1
                deque<int> dq;  // indices into arr
                
                int left = 0;  // left boundary of current window (inclusive)
                for (int k = 0; k < sz; k++) {
                    int winLeft = k - (int)addLim;
                    int winRight = k + (int)remLim;
                    
                    // Add all indices from left up to min(winRight, sz-1) to deque
                    while (left <= winRight && left < sz) {
                        if (arr[left] != NEG_INF) {
                            while (!dq.empty() && arr[dq.back()] <= arr[left]) {
                                dq.pop_back();
                            }
                            dq.push_back(left);
                        }
                        left++;
                    }
                    
                    // Remove indices outside window [winLeft, winRight]
                    while (!dq.empty() && dq.front() < winLeft) {
                        dq.pop_front();
                    }
                    
                    // Get maximum
                    if (!dq.empty()) {
                        newDp[pos[k]] = max(newDp[pos[k]], arr[dq.front()] + k);
                    }
                }
            } else {
                // uplift < 0
                // Window is [k - remLim, k + addLim]
                // We want max of (val[k'] + k') over this window, then subtract k
                
                vector<ll> arr(sz);
                for (int k = 0; k < sz; k++) {
                    arr[k] = (val[k] == NEG_INF) ? NEG_INF : val[k] + k;
                }
                
                deque<int> dq;
                int left = 0;
                for (int k = 0; k < sz; k++) {
                    int winLeft = k - (int)remLim;
                    int winRight = k + (int)addLim;
                    
                    while (left <= winRight && left < sz) {
                        if (arr[left] != NEG_INF) {
                            while (!dq.empty() && arr[dq.back()] <= arr[left]) {
                                dq.pop_back();
                            }
                            dq.push_back(left);
                        }
                        left++;
                    }
                    
                    while (!dq.empty() && dq.front() < winLeft) {
                        dq.pop_front();
                    }
                    
                    if (!dq.empty()) {
                        newDp[pos[k]] = max(newDp[pos[k]], arr[dq.front()] - k);
                    }
                }
            }
        }
        dp = newDp;
    }
    
    // Check if we can achieve delta
    int targetIdx = offset + delta;
    if (targetIdx < 0 || targetIdx >= dpSize || dp[targetIdx] == NEG_INF) {
        cout << "impossible\n";
    } else {
        cout << greedyCount + dp[targetIdx] << "\n";
    }
    
    return 0;
}
#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...
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...