记录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 瓶;
小杨很渴,所以他想要购买总容量不低于 L 的饮料;
小杨勤俭节约,所以在 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_lc[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 → 无解 ✅
⚠️ 关键细节
-
为什么
dp[0] = 0?- 全局数组自动初始化为 0,正好表示"不买任何饮料,容量 0,花费 0"
-
为什么上界是
L + max_l?- 如前所述,超过这个值的解一定不是最优(可删一瓶仍满足 ≥L)
-
空间是否足够?
num = 1e6 + 2000 ≈ 1.002e6,int dp[1e6]≈ 4MB,内存允许
-
时间复杂度?
- 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 |
这段代码利用背包思想和上界剪枝,高效解决了"最小费用满足最低容量"的组合优化问题