题解:洛谷 P15256 Purchasing Milk

【题目来源】

洛谷:P15256 [USACO26JAN2] Purchasing Milk B - 洛谷

【题目描述】

在国家牛奶日,农夫约翰正在提供桶装牛奶的独家优惠!他有 \(N\)\(1 \leq N \leq 10^5\))个交易,编号从 \(1\)\(N\)。对于第 \(i\) 个交易,他以 \(a_i\)\(1 \leq a_i \leq 10^9\)\(a_i < a_{i+1}\))牛币的价格提供 \(2^{i-1}\) 桶牛奶。同一个交易可以被购买任意非负整数次。

你正在考虑 \(Q\)\(1 \leq Q \leq 10^4\))个独立的询问。对于每个询问,你心中有一个整数 \(x\)\(1 \leq x \leq 10^9\)),你想知道购买至少 \(x\) 桶牛奶的最小成本。

【输入】

第一行包含两个整数 \(N\)\(Q\)

接下来的一行包含 \(a_1, a_2, \ldots, a_N\)

接下来的 \(Q\) 行,每行包含一个整数 \(x\),表示一个询问。

【输出】

对于每个询问,在新的一行输出最小成本。

注意:本题中可能涉及大整数,需要使用 64 位整数数据类型(例如,C/C++ 中的 long long)。

【输入样例】

2 4
10 15
1
2
6
7

【输出样例】

10
15
45
55

【核心思想】

  1. 问题分析:给定 \(N\) 个交易,第 \(i\) 个交易以 \(a_i\) 的价格提供 \(2^{i-1}\) 桶牛奶,同一交易可购买任意非负整数次。对于每个询问 \(x\),求购买至少 \(x\) 桶牛奶的最小成本。这是一个贪心 + 二进制分解问题,关键在于利用 \(2^{i-1}\) 的指数性质进行二进制拆分。

  2. 算法选择

    • 价格预处理:确保 \(a_i \leq 2 \cdot a_{i-1}\),否则用两个第 \(i-1\) 交易代替一个第 \(i\) 交易更优
    • 二进制贪心:从大到小枚举每个交易,优先使用性价比高的交易
    • 边界处理:当 \(i \geq 32\) 时,\(2^{i-1} > 10^9\),只需记录最小价格作为保底答案
  3. 关键步骤

    • 预处理价格\(i = 2\)\(N\)):
      • a[i] = min(a[i], 2 * a[i-1]):确保价格合理性
      • \(i \geq 32\),更新 minn = min(minn, a[i]):记录大交易的最低价格
    • 处理每个查询 \(x\)
      • 初始化 ans = minn(保底答案),sum = 0(当前花费)
      • 二进制贪心\(i\)\(\min(31, N)\) 递减到 \(1\)):
        • 计算 base = 2^{i-1}(第 \(i\) 个交易提供的牛奶数)
        • sum += a[i] * (x / base):尽可能多地购买第 \(i\) 个交易
        • 计算候选答案 t = sum + (x \% base ? a[i] : 0):若有余数则多买一个
        • ans = min(ans, t):更新最小花费
        • x \%= base:更新剩余需求量
      • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N + Q \cdot \log x)\),预处理 \(O(N)\),每个查询二进制贪心 \(O(\log x)\)(最多31位)
    • 空间复杂度:\(O(N)\),存储价格数组
  5. 二进制贪心与价格预处理的核心思想

    • 价格合理性:若 \(a_i > 2 \cdot a_{i-1}\),则购买两个第 \(i-1\) 交易(提供 \(2 \cdot 2^{i-2} = 2^{i-1}\) 桶)比买一个第 \(i\) 交易更便宜
    • 二进制分解:利用 \(2^{i-1}\) 的性质,任何数量 \(x\) 都可表示为若干个 \(2^{i-1}\) 之和,类似二进制表示
    • 贪心策略:从大到小枚举交易,优先使用"单位牛奶成本"最低的大交易
    • 保底策略:当 \(i \geq 32\) 时,\(2^{i-1} \geq 2^{31} > 10^9 \geq x\),只需买一个即可满足任何需求,取其中最便宜的作为保底
    • 适用于指数级数量增长、需要凑出目标值的贪心问题

【算法标签】

普及 #贪心

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005;
int n, q, ans, sum, minn = 1e18, x;  // n: 物品数, q: 查询数, x: 查询目标
int a[N];  // 物品价格

signed main()
{
    cin >> n >> q;  // 输入物品数和查询数
    for (int i = 1; i <= n; i++)  // 输入物品价格
    {
        cin >> a[i];
    }
    for (int i = 2; i <= n; i++)  // 预处理价格
    {
        a[i] = min(a[i], 2 * a[i - 1]);  // 确保价格合理
        if (i >= 32)  // 只考虑i≥32的情况
        {
            minn = min(minn, a[i]);  // 记录最小价格
        }
    }
    while (q--)  // 处理查询
    {
        cin >> x;  // 输入目标数量
        ans = minn;  // 初始答案
        sum = 0;  // 当前花费
        for (int i = min(31LL, n); i >= 1; i--)  // 从大到小处理
        {
            int base = pow(2, i - 1);  // 2^(i-1)
            sum += a[i] * (x / base);  // 用第i个物品填充
            int t = sum;
            if (x % base)  // 如果还有剩余
            {
                t += a[i];  // 多买一个
            }
            ans = min(ans, t);  // 更新最小花费
            x %= base;  // 更新剩余需求
        }
        cout << ans << endl;  // 输出答案
    }
    return 0;
}

【运行结果】

2 4
10 15
1
10
2
15
6
45
7
55
posted @ 2026-06-14 10:48  团爸讲算法  阅读(6)  评论(0)    收藏  举报