1. 程式人生 > >P3092 [USACO13NOV]沒有找零No Change

P3092 [USACO13NOV]沒有找零No Change

cpp 順序 結合 浪費 hang cout cin tdi !=


首先可以想到 枚舉 用硬幣的順序.(\(O(K!)\))
假如硬幣順序確定了, 我們只需要確定每一個硬幣買哪一段即可.
發現可以貪心, 即每一個硬幣都盡量買, 直到剩余價值不夠為止.
證明很簡單: 假如我這個硬幣可以多買某一個商品但是沒有買, 只可能是為了用下一個硬幣去買這個商品來讓下一個硬幣浪費的少一些.
但是下一個硬幣少浪費的其實等於這個硬幣多浪費的, 也就是這個商品的價格. 換言之, 這樣不會更好, 但有可能更差(錢不夠).
於是, 算出前綴和以後大力二分每一個硬幣用到哪兒即可.
理論復雜度\(O(K!KlogN)\), 顯然無法承受.

考慮狀態壓縮優化. 用一個數組 f[1 << 16]

記錄下某一個狀態時浪費價值的最小值.
因為對於給定的狀態, 金幣的總價值確定, 可以很容易的結合記錄下來的值推出當前買到哪一個商品, 再用前文所提到的二分更新狀態.
理論復雜度\(O(2^N logN)\)

#include <cstdio>
#include <cstring>
#include <cassert>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int INF = 0x7f7f7f7f;
const int MAXN = 1e5 + 10;
inline int read()
{
    int x = 0; char ch = getchar();
    while(!isdigit(ch)) ch = getchar();
    while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
    return x;
}

int N, K;
ll tot[1 << 16];
int c[MAXN], a[MAXN]; ll sum[MAXN];
int f[1 << 16];

int main()
{
    cin>>K>>N;
    for(int i = 0; i < K; i++) c[i] = read();
    for(int i = 1; i <= N; i++) a[i] = read();
    for(int i = 1; i <= N; i++) sum[i] = sum[i - 1] + a[i];
    sum[N + 1] = (1LL << 60); 
    for(int i = 1; i < (1 << K); i++) 
        for(int j = 0; j < K; j++) if(i & (1 << j)) tot[i] += c[j];

    memset(f, 0x7f, sizeof(f)); f[0] = 0;
    ll ans = -(1LL << 60);
    for(int state = 0; state < (1 << K); state++) if(f[state] != INF){
        int cur = lower_bound(sum, sum + N + 2, tot[state] - f[state]) - sum;

        for(int i = 0; i < K; i++) if(!(state & (1 << i))){
            int tmp = (state | (1 << i));
            int nxt = upper_bound(sum, sum + N + 2, sum[cur] + c[i]) - sum;
            --nxt;
            f[tmp] = min(1LL * f[tmp], f[state] + c[i] - (sum[nxt] - sum[cur]));
            if(nxt == N) ans = max(ans, tot[((1 << K) - 1) ^ tmp]);
        }
    }
    if(ans == -(1LL << 60)) puts("-1");
    else cout<<ans<<endl;
    return 0;
}

P3092 [USACO13NOV]沒有找零No Change