B3873 [GESP202309 六级] 小杨买饮料

记录94

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int num=1e6+2000;
int c[510],l[510],dp[num];//01背包dp
int main(){
	int N,L;
	cin>>N>>L;
	int max_l=0;
	for(int i=0;i<N;i++){
		cin>>c[i]>>l[i];
		max_l=max(max_l,l[i]);
	}//找到最大瓶饮料
	int MAXL=max_l+L;//容量为最小容量加上最大瓶
	for(int i=1;i<=MAXL;i++) dp[i]=INT_MAX;//从1到最大容量,价格无限大不可达,方便更新最小花费
	for(int i=0;i<N;i++){//从 MAXL 倒着更新到 l[i],防止重复选择
		for(int j=MAXL;j>=l[i];j--){
			if(dp[j-l[i]]!=INT_MAX) dp[j]=min(dp[j],dp[j-l[i]]+c[i]);
		}//为最大钱,则代表无构成这个钱数的路径
	}//如果 dp[j - l[i]] 可达(非 INT_MAX),则尝试用它更新 dp[j]
	int ans=INT_MAX;
	for(int i=L;i<=MAXL;i++) ans=min(ans,dp[i]);
	if(ans==INT_MAX) cout<<"no solution";
	else cout<<ans;
    return 0;
}

题目传送门https://www.luogu.com.cn/problem/B3873


突破口

小杨来到了一家商店,打算购买一些饮料。这家商店总共出售 N 种饮料,编号从 0 至 N−1,其中编号为 i 的饮料售价 ci​ 元,容量 li​ 毫升。

小杨的需求有如下几点:

  1. 小杨想要尽可能尝试不同种类的饮料,因此他希望每种饮料至多购买 1 瓶;

  2. 小杨很渴,所以他想要购买总容量不低于 L 的饮料;

  3. 小杨勤俭节约,所以在 1 和 2 的前提下,他希望使用尽可能少的费用。

方便起见,你只需要输出最少花费的费用即可。特别地,如果不能满足小杨的要求,则输出 no solution


思路

题目核心理解

🎯 问题本质

这是一个 0-1 背包变种问题

  • 有 N 种饮料(每种最多选 1 瓶)
  • 每瓶饮料有 费用 c[i]容量 l[i]
  • 目标:总容量 ≥ L 的前提下,总费用最小

✅ 这不是"恰好装满",而是"至少装满 L",且要最小化代价。

❌ 无解情况

如果所有饮料的总容量 < L → 无法满足 → 输出 "no solution"


🧠 二、解题思路:0-1 背包(最小费用版本)

常规背包 vs 本题

表格

类型 状态定义 目标
经典 0-1 背包 dp[j] = 容量 j 下最大价值 最大化价值
本题 dp[j] = 总容量恰好为 j 时的最小费用 最小化费用

关键技巧:上界剪枝

  • 我们不需要考虑容量超过 L + max_l 的情况!
  • 为什么?
    • 假设最优解总容量 > L + max_l
    • 那么一定可以去掉某一瓶饮料(容量 ≤ max_l),使得剩余容量 ≥ L
    • 而费用会更少或不变 → 与"最优"矛盾
  • 所以:只需计算到 MAXL = L + max(l[i]) 即可

💡 这个 trick 极大缩小了 DP 范围,避免 L 很小但 l[i] 很大导致数组爆炸

状态转移(0-1 背包)

cpp 复制代码
for i in 0..N-1:
    for j = MAXL downto l[i]:
        if dp[j - l[i]] != INF:
            dp[j] = min(dp[j], dp[j - l[i]] + c[i])

初始化

  • dp[0] = 0(容量 0 花费 0)
  • 其他 dp[j] = INF(表示不可达)

最终答案

  • j ∈ [L, MAXL] 中找 min(dp[j])
  • 若仍为 INF → 无解

代码分析

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int num = 1e6 + 2000; // 最大可能容量:L(≤2000) + max_l(≤1e6) → 1e6+2000 足够
int c[510], l[510], dp[num]; // N≤500
  • num 是 DP 数组大小,覆盖 L + max_l
  • c[i], l[i] 存储第 i 种饮料的价格和容量
cpp 复制代码
int main(){
    int N, L;
    cin >> N >> L;
    int max_l = 0;
    for(int i = 0; i < N; i++){
        cin >> c[i] >> l[i];
        max_l = max(max_l, l[i]); // 记录最大单瓶容量
    }
  • 读入数据,同时找出 max_l
cpp 复制代码
    int MAXL = max_l + L; // 关键优化:只需计算到 L + max_l
cpp 复制代码
    for(int i = 1; i <= MAXL; i++) 
        dp[i] = INT_MAX; // 初始化为"不可达"
    // 注意:dp[0] 未显式赋值,但全局变量默认为 0 → 正确!
  • dp[0] = 0(隐含)
  • 其他容量初始为无穷大(表示无法达到)
cpp 复制代码
    for(int i = 0; i < N; i++){ // 遍历每种饮料
        for(int j = MAXL; j >= l[i]; j--){ // 0-1 背包倒序更新
            if(dp[j - l[i]] != INT_MAX) // 如果 j-l[i] 可达
                dp[j] = min(dp[j], dp[j - l[i]] + c[i]);
        }
    }
  • 标准 0-1 背包写法
  • 倒序防止重复选择同一物品
  • 只有前驱状态可达时才更新
cpp 复制代码
    int ans = INT_MAX;
    for(int i = L; i <= MAXL; i++)
        ans = min(ans, dp[i]); // 找总容量 ≥ L 的最小花费
cpp 复制代码
    if(ans == INT_MAX) 
        cout << "no solution";
    else 
        cout << ans;
    return 0;
}

🧪 样例验证

样例 1:N=5, L=100

饮料:

cpp 复制代码
(100,2000), (2,50), (4,40), (5,30), (3,20)
  • max_l = 2000, MAXL = 100 + 2000 = 2100
  • DP 会计算出:
    • 容量 100:可能方案如 50+30+20=100 → cost=2+5+3=10
    • 容量 110:50+40+20=110 → cost=2+4+3=9 ← 更优
  • 最终 ans = 9

样例 2:L=141

  • 小容量饮料总和 = 50+40+30+20 = 140 < 141 → 不够
  • 必须买 (100,2000) → cost=100 ✅

样例 3:没有大容量饮料 → 总容量=140 < 141 → 无解 ✅


⚠️ 关键细节

  1. 为什么 dp[0] = 0

    • 全局数组自动初始化为 0,正好表示"不买任何饮料,容量 0,花费 0"
  2. 为什么上界是 L + max_l

    • 如前所述,超过这个值的解一定不是最优(可删一瓶仍满足 ≥L)
  3. 空间是否足够?

    • num = 1e6 + 2000 ≈ 1.002e6int dp[1e6] ≈ 4MB,内存允许
  4. 时间复杂度?

    • O(N × MAXL) = 500 × (2000 + 1e6) ≈ 5e8 → 看似超时?

    ❗ 但注意:题目中 70% 数据满足 l[i] ≤ 100

    • 此时 max_l ≤ 100, MAXL = L + 100 ≤ 2100

    • 实际复杂度 ≈ 500 × 2100 ≈ 1e6,非常快

    • 对于 l[i] 很大的情况(如 1e6),虽然 MAXL ≈ 1e6,但 N=500 → 5e8 操作在 C++ 中可能卡常

    • 但题目数据范围允许(或测试点较弱),且这是标准解法


总结

要点 说明
问题类型 0-1 背包(最小费用,容量至少 L)
状态定义 dp[j] = 恰好容量 j 的最小花费
关键优化 上界设为 L + max_l,大幅减少状态数
初始化 dp[0]=0,其余 INF
答案 `min{ dp[j]
无解判断 最小值仍为 INF

这段代码利用背包思想和上界剪枝,高效解决了"最小费用满足最低容量"的组合优化问题

相关推荐
2301_789015622 小时前
C++11新增特性:可变参数模板、lambda表达式、function包装器、bind绑定、defult和delete
c语言·开发语言·c++·算法·c++11·万能引用
Ahtacca2 小时前
基于决策树算法的动物分类实验:Mac环境复现指南
python·算法·决策树·机器学习·ai·分类
x_xbx2 小时前
LeetCode:567. 字符串的排列
算法·leetcode·职场和发展
沛沛rh452 小时前
力扣 42. 接雨水 - 高效双指针解法(Rust实现)详细题解
算法·leetcode·rust
tankeven2 小时前
HJ158 挡住洪水
c++·算法
梓䈑2 小时前
【CMake】动静态库的安装 和 使用
c++·cmake
PyHaVolask2 小时前
顺序栈:基于数组的实现
数据结构·顺序栈
Wect2 小时前
LeetCode 190. 颠倒二进制位:两种解法详解
前端·算法·typescript
刘永鑫Adam2 小时前
BiB | 蒋超实验室开发 Kun-peng(鲲鹏):实现可扩展且准确的泛域宏基因组分类
人工智能·算法·机器学习·分类·数据挖掘