P14635 [NOIP2025] 糖果店 / candy(官方数据)
greedy, binary search, https://www.luogu.com.cn/problem/P14635
小 X 开了一家糖果店,售卖
小 R 带了
输入格式
输入的第一行包含两个正整数
输入的第
输出格式
输出一行一个非负整数,表示
输入 #1
2 10
4 1
3 3输出 #1
4输入 #2
3 15
1 7
2 3
3 1输出 #2
8说明/提示
【样例 1 解释】
小 R 可以购买 4 颗第一种糖果,共花费
【样例 2 解释】
小 R 可以购买 1 颗第一种糖果、1 颗第二种糖果与 6 颗第三种糖果,共花费
【样例 3】
见选手目录下的 candy/candy3.in 与 candy/candy3.ans。
该样例满足测试点
【样例 4】
见选手目录下的 candy/candy4.in 与 candy/candy4.ans。
该样例满足测试点
【样例 5】
见选手目录下的 candy/candy5.in 与 candy/candy5.ans。
该样例满足测试点
【样例 6】
见选手目录下的 candy/candy6.in 与 candy/candy6.ans。
该样例满足测试点
【样例 7】
见选手目录下的 candy/candy7.in 与 candy/candy7.ans。
该样例满足测试点
【数据范围】
对于所有测试数据,均有:
; ; - 对于所有
,均有 。
::cute-table
| 测试点编号 | 特殊性质 | ||
|---|---|---|---|
| 无 | |||
| ^ | |||
| ^ | ^ | ||
| A | |||
| ^ | ^ | B | |
| ^ | ^ | 无 | |
| A | |||
| ^ | ^ | B | |
| ^ | ^ | 无 | |
| A | |||
| ^ | ^ | B | |
| ^ | ^ | 无 | |
| ^ | ^ |
特殊性质 A:对于所有
特殊性质 B:对于所有
这里是针对 P14635 [NOIP2025] 糖果店 (Candy) 的 Python 解决方案及详细中文解释。
核心思路
这个问题本质上是一个贪心 (Greedy) 问题,结合了枚举。
成本周期分析: 每种糖果的价格是循环的:
。 购买 2 颗(一对)的总花费是 。 如果我们需要大量购买糖果,显然应该优先选择 最小的那一种,我们称之为“主力糖果”( best)。购买策略:
- 对于主力糖果(
最小的那种):我们可以无限购买,成对购买最划算。 - 对于非主力糖果:我们只考虑买第一颗(价格
)。 - 为什么不买第二颗?因为买第二颗意味着为了凑齐一对,花费了
。由于 ,这笔钱不如拿去买主力糖果的一对更划算(或者一样划算,那归为主力也不影响)。
- 为什么不买第二颗?因为买第二颗意味着为了凑齐一对,花费了
- 特例处理:如果
最小的有多种,我们选择 最小的作为主力(因为 小意味着买单颗更容易,对凑零头有利)。
- 对于主力糖果(
算法流程:
- 遍历所有糖果,找到
最小的种类,记为 best。 - 将所有非
best种类的第一颗价格放入一个列表 singles并从小到大排序。 - 枚举:我们可以选择购买
singles列表中最便宜的个( )。 - 计算买这
个单颗糖果的花费。 - 用剩余的钱去买主力糖果
best。- 先尽可能买“对”(每对
)。 - 剩下的钱如果够买一颗
,则再加 1 颗。
- 先尽可能买“对”(每对
- 记录总数量,取所有
情况下的最大值。
- 计算买这
- 遍历所有糖果,找到
Python 代码
import sys
def solve():
# 使用 sys.stdin 读取所有输入,处理大数据量时更快
input_data = sys.stdin.read().split()
if not input_data:
return
iterator = iter(input_data)
try:
n = int(next(iterator))
m = int(next(iterator))
except StopIteration:
return
# 读取糖果数据
# 存储格式: [(x1, y1), (x2, y2), ...]
candies = []
for _ in range(n):
x = int(next(iterator))
y = int(next(iterator))
candies.append((x, y))
# 1. 找到“主力糖果” (best)
# 标准:x+y 最小。如果 x+y 相同,则 x 越小越好(因为剩下的零钱更容易买到第一颗)
min_s = float('inf')
best_idx = -1
for i in range(n):
current_s = candies[i][0] + candies[i][1]
if current_s < min_s:
min_s = current_s
best_idx = i
elif current_s == min_s:
# 如果总和相同,选 x 更小的
if candies[i][0] < candies[best_idx][0]:
best_idx = i
best_x, best_y = candies[best_idx]
best_s = best_x + best_y
# 2. 收集其他种类的第一颗糖果价格 (singles)
singles = []
for i in range(n):
if i == best_idx:
continue
singles.append(candies[i][0])
# 排序,以便贪心地选取便宜的单颗
singles.sort()
max_count = 0
# 3. 枚举策略:先计算完全不买其他单颗的情况 (k=0)
# 计算主力糖果能买多少
# 剩余钱能买几对 * 2
count_from_best = (m // best_s) * 2
rem_m = m % best_s
# 如果剩余钱够买主力糖果的第一颗
if rem_m >= best_x:
count_from_best += 1
max_count = count_from_best
# 4. 枚举购买 k 个其他单颗糖果的情况
current_spent = 0
for k in range(len(singles)):
# 买第 k+1 便宜的单颗
current_spent += singles[k]
# 如果钱不够了,提前结束
if current_spent > m:
break
# 剩余预算
rem_m = m - current_spent
# 用剩余预算买主力糖果
# 能买多少对
pairs = rem_m // best_s
cnt = pairs * 2
# 买完对后剩下的零钱
leftover = rem_m % best_s
# 够不够买主力的单颗
if leftover >= best_x:
cnt += 1
# 总数量 = 已买的 k+1 个单颗 + 主力糖果数量
total_candies = (k + 1) + cnt
if total_candies > max_count:
max_count = total_candies
print(max_count)
if __name__ == '__main__':
solve()复杂度分析
时间复杂度:
- 寻找
best糖果:遍历一次。 - 收集
singles并排序:。 - 枚举循环:循环
次,内部计算为 ,总共 。 - 总体时间复杂度为
。 - 对于
,计算量约为 级别,Python 可以轻松在 1 秒内完成。
- 寻找
空间复杂度:
- 存储糖果列表和单颗价格列表:
。
- 存储糖果列表和单颗价格列表:
详细解释 (针对样例 2)
输入:
3 15
1 7
2 3
3 1步骤:
计算每种糖果的一对成本:
- 类型 1:
- 类型 2:
- 类型 3:
主力糖果是类型 3 (Sum = 4),
。 - 类型 1:
其他单颗列表
singles:- 类型 1 的第一颗: 1
- 类型 2 的第一颗: 2
singles=[1, 2](排序后)
枚举过程:
情况 k=0 (不买其他单颗):
- 预算 15 全买类型 3。
- 15 // 4 = 3 对 (花费 12,得 6 颗)。
- 剩余 3 元。
(类型 3 第一颗价),再买 1 颗。 - 总计: 7 颗。
情况 k=1 (买 1 个其他单颗):
- 买
singles[0](类型 1,价格 1)。花费 1。 - 剩余预算:
。 - 全买类型 3:
对 (花费 12,得 6 颗)。 - 剩余 2 元。
,买不起类型 3 的单颗。 - 总计:
颗。
- 买
情况 k=2 (买 2 个其他单颗):
- 买
singles[0], singles[1](价格 1, 2)。花费。 - 剩余预算:
。 - 全买类型 3:
对 (花费 12,得 6 颗)。 - 剩余 0 元。
- 总计:
颗。
- 买
结果: 最大值是 8。