专栏文章
题解:B3873 [GESP202309 六级] 小杨买饮料
B3873题解参与者 4已保存评论 3
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 3 条
- 当前快照
- 1 份
- 快照标识符
- @mip63afa
- 此快照首次捕获于
- 2025/12/03 06:46 3 个月前
- 此快照最后确认于
- 2025/12/03 06:46 3 个月前
前置知识
题目大意
题目分析
有 个物品,每个物品的价值为它的容量 ,价值为它的售价 ,求在容量不小于 的情况下花费的最小值。如果我们直接用转移方程的板子 的话会超时,因为 非常大,可达 ,所以我们必须考虑优化。
优化
第一,如果 ,我们就可以把 设为 ,避免超时(剪枝优化)。
第二,我们可以发现背包的容量为 ,并且如果选择出来的物品的 之和大于 ,那它一定不是最优方案。
证明背包容量为 (反证法)
-
假设存在最优解
设 是最优解的总容量,且 ,对应的最小花费为 。 -
移除部分物品
由于每个物品的容量 (经过剪枝优化),我们可以从 中逐个移除物品,直到剩余容量 满足 。
移除过程中,剩余容量始终 (初始 ,每次移除物品 ,故 )。 -
花费对比
设移除物品后的总花费为 ,则:所以不等号成立:因 (物品花费非负),移除物品不会增加花费。 -
剩余容量范围
移除过程结束时: -
推出矛盾
- 且 ,说明 是一个合法解。
- 但 与 " 是最小花费"矛盾(若 ,则 非最优;若 ,则 是更优解,因其容量更接近 )。
故结论得证。
继续分析
然后我们就可以按照上面的思路开始 DP 了,然后从 到 遍历一下 数组,然后 的最小值就是答案。
无解情况:
-
,即 所有饮料之和都不能满足需求,就输出
no solution。 -
把 初始化为一个大值,如果遍历完 数组 仍为最大值,则也无解。
还有,注意 数组要初始为一个大值。
答疑
Q:为什么要从 遍历到 ?
A:因为只有大于等于 的方案才合法,并且对于以下数据来说
CPP5 100
1 2000
10 100
20 80
40 20
10000 1
购买的饮料总容量可能大于 ,这种情况下如果写 不能覆盖到全部,但写 就可以。
代码实现
CPP#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define TRACE 1
#define tcout TRACE && cout
#define int long long
//十年OI一场空,不开long long见祖宗(其实本题没必要)
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);//快读
#define pri priority_queue
const int P = 998244353;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6+10, M = 1e8 + 10;
int n,L;//n为物品数量,L为需要的数量
int c[N],l[N];//含义见题目
int dp[N];
int cnt=0;//饮料总量
signed main()
{
fst
memset(dp,0x3f,sizeof(dp));//数组初始化
cin>>n>>L;
for(int i=1;i<=n;i++){
cin>>c[i]>>l[i];
cnt+=l[i];
l[i]=min(l[i],L);
}
if(cnt<L){
cout<<"no solution";//无解1
return 0;
}
dp[0]=0;
for(int i=1;i<=n;i++){
for(int j=2*L;j>=l[i];j--){
dp[j]=min(dp[j],dp[j-l[i]]+c[i]);//板子
}
}
int ans=INF;
for(int i=L;i<=2*L;i++){
ans=min(ans,dp[i]);
}
if(ans==INF){
cout<<"no solution";//无解2
return 0;
}
cout<<ans;
return 0;
}
UPD
2025/6/2 改正了 函数写法不当的问题。
2025/6/2 证明了背包容量为 。
相关推荐
评论
共 3 条评论,欢迎与作者交流。
正在加载评论...