题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 个交易,第 \(i\) 个交易以 \(a_i\) 的价格提供 \(2^{i-1}\) 桶牛奶,同一交易可购买任意非负整数次。对于每个询问 \(x\),求购买至少 \(x\) 桶牛奶的最小成本。这是一个贪心 + 二进制分解问题,关键在于利用 \(2^{i-1}\) 的指数性质进行二进制拆分。
-
算法选择:
- 价格预处理:确保 \(a_i \leq 2 \cdot a_{i-1}\),否则用两个第 \(i-1\) 交易代替一个第 \(i\) 交易更优
- 二进制贪心:从大到小枚举每个交易,优先使用性价比高的交易
- 边界处理:当 \(i \geq 32\) 时,\(2^{i-1} > 10^9\),只需记录最小价格作为保底答案
-
关键步骤:
- 预处理价格(\(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
- 初始化
- 预处理价格(\(i = 2\) 到 \(N\)):
-
时间/空间复杂度:
- 时间复杂度:\(O(N + Q \cdot \log x)\),预处理 \(O(N)\),每个查询二进制贪心 \(O(\log x)\)(最多31位)
- 空间复杂度:\(O(N)\),存储价格数组
-
二进制贪心与价格预处理的核心思想:
- 价格合理性:若 \(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
浙公网安备 33010602011771号