嘘~ 正在从服务器偷取页面 . . .

洛谷P3092 [USACO13NOV] No Change G 题解


洛谷P3092 [USACO13NOV] No Change G 题解

题目链接:P3092 [USACO13NOV] No Change G

题意

Farmer John 在市场上为他的农场购买物资。 他的口袋里有 $k$ 个硬币 $(1 \le k \le 16)$ ,每个硬币的价值在$1\sim 10^8$ 范围内。 FJ 想进行一连串的 $n$ 次购买 $(1 \le n \le 10^5)$ ,其中第 $1$ 次购买需要花费 $c_i$ 个单位的钱$(1 \le c_i \le 10^4)$ 。 当他进行这一连串的购买时,他可以定期停下来,用一枚硬币来支付自他上次付款以来的所有购买(当然,他使用的这枚硬币必须足够大,以支付所有这些购买)。 不幸的是,市场上的小贩完全没有零钱,所以每当 FJ 使用大于他所欠的钱的硬币时,他都很遗憾地没有收到任何零钱的回报!请计算出最大的金额。

请计算 FJ 在依次进行了 $n$ 次购买后,最终能得到的最大金额。 如果 FJ 不可能完成他所有的购买,则输出 $-1$。

输入格式

第 $1$ 行,两个整数,$k$ 和 $n$ 。

第 $2\ \sim \ k+1$ 行,每一行包含 FJ 的一个硬币的金额。

第 $2+k\ \sim \ n + k + 1$ 行,这 $n$ 行包含 FJ 打算购买的东西的费用。

输出格式

第1行,FJ 最终能得到的最大金额,如果 FJ 不能完成所有的购买,则输出 $-1$。

得到的最大金额一定时,硬币的状态不唯一,因此不能二分。

于是考虑枚举所有的状态,但是每次都暴力检验复杂度太高了

注意到很多的状态之间存在联系,比如某个状态不能成功购买,但是后面加上了几个硬币就能买了

设 $f_i$ 表示状态为 $i$ 时最多能买到几号物品。不妨记每个硬币的面额为 $v_i$ 。

状态可以转移至 $f_j$ 当且仅当 $j = i\, \cup\,\{k\},~k \in (\sim i)$ 。记 $r=p$ 为使得 $\sum_{t = f_i}^{r} v_t \le w_k$ 成立的最大值,则

这个 $p$ 可以二分得到,只需要预处理下 $v_i$ 的前缀和。并且当某个 $f_i = n$ 时,尝试更新一下当前的答案。

时间复杂度 $\mathcal{O}(2^kk \log n)$

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))
#define M ((int)((1 << 16) + 16))

int n,k,res,sum[N],mon[N],f[M];
int find(int l,int r,int s)
{
    for(int mid, now = sum[l]; l < r; )
    {
        mid = (l + r + 1) >> 1;
        if(sum[mid] - now <= s) l = mid; else r = mid - 1;
    }
    return l;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    
    cin >> k >> n;
    for(int i = 1; i <= k; i++) cin >> mon[i];
    for(int i = 1; i <= n; i++) { cin >> sum[i], sum[i] += sum[i - 1]; }
    int mx = (1ll << k) - 1, ok = 0;
    for(int i = 0; i <= mx; i++)
    {
        for(int j = 0; j < k; j++)
        {
            if((i >> j) & 1) continue;
            up(f[i | (1ll << j)], find(f[i], n, mon[j + 1]));
        }
        if(f[i] == n && (ok = 1))
        {
            int cnt = 0;
            for(int j = 0; j < k; j++)
                if(((i >> j) & 1) == 0) cnt += mon[j + 1];
            up(res, cnt);
        }
    }
    cout << (ok ? res : -1) << '\n';
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/blog/sflsrick/solution-p3092


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录