USACO13NOV No Change G

题目链接

Solution

状压 dp 题:状态 \(S\) 一共有 \(k\) 位,表示当前硬币是否已经用过了。设 \(f_S\) 表示状态为 \(S\) 时最多能购买多少物品。

考虑把 \(S\) 中的一个 \(0\) 变成 \(1\):需要使用当前硬币,从 \(f_S\) 开始购买尽可能多的物品。为了保证效率,这一过程可以用前缀和 + 二分查找来维护。如果所有硬币都用完后购买不到 \(n\) 个物品,输出无解;否则,设 \(num_S\) 表示状态 \(S\) 使用了多少金币,答案为所有 \(f_S=n\)\(num_S\) 的最小值。

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
const int N = 2000000;
int n, k, max_ = 0, a[100], num[N], b[N], f[N], sum[N];
 
int check(int x, int y)
{
    int l = x, r = n;
    while(l + 1 < r)
    {
        int mid = (l + r) / 2;
        if(sum[mid] - sum[x] > y) r = mid;
        else l = mid;
    }
    if(sum[r] - sum[x] <= y) return r;
    return l;
}
 
int main()
{
    scanf("%d%d", &k, &n);
    for(int i = 1; i <= k; i++) scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++) scanf("%d", &b[i]), sum[i] = sum[i - 1] + b[i];
    memset(f, 0, sizeof(f));
    for(int i = 0; i < (1 << k); i++)
        for(int j = 1; j <= k; j++)
        {
            if((i & (1 << (j - 1))) != 0) continue;
            num[i] += a[j];
            f[i | (1 << (j - 1))] = max(f[i | (1 << (j - 1))], check(f[i], a[j]));
        }
    if(f[(1 << k) - 1] < n) { printf("-1"); return 0; }
    for(int i = 0; i < (1 << k); i++) if(f[i] == n) max_ = max(max_, num[i]);
    printf("%d", max_);
    return 0;
} 
posted @ 2020-03-14 20:29  Nyxia  阅读(144)  评论(0编辑  收藏  举报