文章目录
题目描述
小杨来到了一家商店,打算购买一些饮料。这家商店总共出售 N N N 种饮料,编号从 0 0 0 至 N − 1 N-1 N−1,其中编号为 i i i 的饮料售价 c i c_i ci 元,容量 l i l_i li 毫升。
小杨的需求有如下几点:
-
小杨想要尽可能尝试不同种类的饮料,因此他希望每种饮料至多购买 1 1 1 瓶;
-
小杨很渴,所以他想要购买总容量不低于 L L L 的饮料;
-
小杨勤俭节约,所以在 1 1 1 和 2 2 2 的前提下,他希望使用尽可能少的费用。
方便起见,你只需要输出最少花费的费用即可。特别地,如果不能满足小杨的要求,则输出 no solution
。
输入格式
第一行两个整数 N , L N,L N,L。
接下来 N N N行,依次描述第 i = 0 , 1 , ⋯ , N − 1 i=0,1,\cdots,N-1 i=0,1,⋯,N−1 种饮料:每行两个整数 c i , l i c_i,l_i ci,li。
输出格式
输出一行一个整数,表示最少需要花费多少钱,才能满足小杨的要求。特别地,如果不能满足要求,则输出 no solution
。
输入输出样例 #1
输入 #1
5 100
100 2000
2 50
4 40
5 30
3 20
输出 #1
9
输入输出样例 #2
输入 #2
5 141
100 2000
2 50
4 40
5 30
3 20
输出 #2
100
输入输出样例 #3
输入 #3
4 141
2 50
4 40
5 30
3 20
输出 #3
no solution
说明/提示
样例 1 解释
小杨可以购买 1 , 2 , 4 1,2,4 1,2,4 号饮料,总计获得 50 + 40 + 20 = 110 50+40+20=110 50+40+20=110 毫升饮料,花费 2 + 4 + 3 = 9 2+4+3=9 2+4+3=9 元。
如果只考虑前两项需求,小杨也可以购买 1 , 3 , 4 1,3,4 1,3,4 号饮料,它们的容量总和为 50 + 30 + 20 = 100 50+30+20=100 50+30+20=100 毫升,恰好可以满足需求。但遗憾的是,这个方案需要花费 2 + 5 + 3 = 10 2+5+3=10 2+5+3=10 元。
样例 2 解释
1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 号饮料总计 140 140 140 毫升,如每种饮料至多购买 1 1 1 瓶,则恰好无法满足需求,因此只能花费 100 100 100 元购买 0 0 0 号饮料。
数据规模
对于 40 % 40\% 40% 的测试点,保证 N ≤ 20 ; 1 ≤ L ≤ 100 ; l i ≤ 100 N \le 20;1\le L \le 100; l_i \le 100 N≤20;1≤L≤100;li≤100。
对于 70 % 70\% 70% 的测试点,保证 l i ≤ 100 l_i \le 100 li≤100。
对于 100 % 100\% 100% 的测试点,保证 1 ≤ N ≤ 500 ; 1 ≤ L ≤ 2000 ; 1 ≤ c i , l i ≤ 1 0 6 1\le N \le 500;1\le L \le 2000; 1\le c_i,l_i \le 10^6 1≤N≤500;1≤L≤2000;1≤ci,li≤106。
提交链接
解析
搜索的想法(80分)
DFS函数的作用:
dfs(idx, vol, cost)
是一个递归函数,用来从当前索引 idx
开始,尝试选择饮料。
vol
是当前已选择饮料的总容量。
cost
是当前已选择饮料的总花费。
如果当前的容量 vol
大于等于 L
,说明已经满足容量要求,就更新最小费用 mi_cost
。
void dfs(int idx , int vol , int cost) //从idx开始选取(每种饮料最多选1瓶) 总容量 总花费
{
//printf("idx--> %d vol--> %d cost--> %d\n" , idx , vol , cost);
if(vol >= L)
{
mi_cost = min(mi_cost , cost);
return;
}
for(int i = idx; i <= n; i++)
dfs(i + 1 , vol + l[i] , cost + c[i]);
}
DFS的递归结构:
在 d f s dfs dfs 函数中,使用的是典型的递归搜索,对于每个饮料,可以选择或者不选择,因此递归的深度是 n n n(即饮料种类数),每层递归选择一个饮料。
对于每个饮料,从 i d x idx idx 开始的所有选择情况都会被遍历到,因此递归的时间复杂度是 O ( 2 n ) O(2^n) O(2n),因为每个饮料都有两种选择(选择或不选择),一共会遍历所有可能的组合。
整个算法的时间复杂度是 O ( 2 n ) O(2^n) O(2n),其中 n n n 是饮料的数量。这个时间复杂度对于较大的 n n n 值来说是不可接受的,因为它的增长非常快。
如果 n n n 较大(比如达到几百个饮料),则推荐使用 动态规划 的方法(比如背包问题的解决方案),可以将时间复杂度降低到 O ( n ∗ L ) O(n * L) O(n∗L),这样就能处理大规模数据。
参考代码:
#include <bits/stdc++.h>
using namespace std;
int n, L, c[509], l[509], tot_vol , mi_cost = 0x3f3f3f3f;
void dfs(int idx , int vol , int cost) //从idx开始选取(每种饮料最多选1瓶) 总容量 总花费
{
//printf("idx--> %d vol--> %d cost--> %d\n" , idx , vol , cost);
if(vol >= L)
{
mi_cost = min(mi_cost , cost);
return;
}
for(int i = idx; i <= n; i++)
dfs(i + 1 , vol + l[i] , cost + c[i]);
}
int main()
{
cin >> n >> L; // n种饮料 总容量不低于L
for (int i = 1; i <= n; i++)
{
cin >> c[i] >> l[i]; // 每一种饮料的售价和容量
tot_vol += l[i]; //计算总容量
}
if(tot_vol < L) //不合法的情况
cout << "no solution";
else
{
//绝对不会出现总容量少于L的情况
dfs(1 , 0 , 0);
cout << mi_cost;
}
return 0;
}
01背包的想法(60分)
每一种饮料至多购买一瓶,考虑 01 01 01 背包。
f [ i ] : f[i]: f[i]: 购买 i i i 升饮料的最小花费。
初始时,我们设置 f [ 0 ] = 0 f[0] = 0 f[0]=0,即购买 0 0 0 毫升饮料的花费为 0 0 0,其他容量的费用设置为较大的数。
动态规划更新:
对于每个饮料 ( c o s t , v o l u m e ) (cost, volume) (cost,volume),我们从后向前更新 f f f 数组,避免重复使用相同的饮料。具体的状态转移是: f [ j ] = m i n ( f [ j ] , f [ j − v o l u m e ] + c o s t ) f[j]=min(f[j],f[j-volume]+cost) f[j]=min(f[j],f[j−volume]+cost)
如果购买 j − v o l u m e j - volume j−volume 毫升的饮料所需的费用已经计算出来,并且这个容量是合法的,那么我们可以用当前饮料来更新 f [ j ] f[j] f[j]。
考虑时间复杂度, n n n 最大为 500 500 500, f f f 数组最大能开到 200000 200000 200000, n ∗ l i n * l_i n∗li 最大为 5 ∗ 1 0 8 5 * 10^8 5∗108,这个思路只能拿 60 60 60 分。
结果:
我们从
f
[
L
]
f[L]
f[L] 到
f
[
200000
]
f[200000]
f[200000] 查找最小费用。如果没有找到符合条件的容量,输出 no solution
;否则输出最小费用。
#include <bits/stdc++.h>
using namespace std;
int n, L, c[509], l[509];
int f[200009]; //f[j]:买总容量为L的饮料的最小花费
int main()
{
cin >> n >> L; // n种饮料 总容量不低于L
for (int i = 1; i <= n; i++)
cin >> c[i] >> l[i]; // 每一种饮料的售价和容量
memset(f , 0x3f , sizeof(f));
f[0] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 200000; j >= l[i]; j--)
f[j] = min(f[j] , f[j - l[i]] + c[i]);
}
int mi_cost = 0x3f3f3f3f;
for(int i = L; i <= 200000; i++)
mi_cost = min(mi_cost , f[i]);
if(mi_cost != 0x3f3f3f3f)
cout << mi_cost;
else
cout << "no solution";
return 0;
}
01背包的变形(100分)
题目中,他想要购买总容量不低于 L L L 的饮料。
f [ i ] : f[i]: f[i]: 购买不低于 i i i 升饮料的最小花费。
在进行状态转移的时候,若当前饮料的容量超过目标容量时,也是需要考虑的。
核心代码:
for (int i = 1; i <= n; i++)
{
for (int j = L; j >= 0; j--)
f[j] = min(f[j], f[max(0, j - l[i])] + c[i]);
}
假设
4
4
4 件商品,买饮料的容量至少为
141
141
141。
第一瓶饮料,售价为
2
2
2,容量为
50
50
50;第二瓶饮料,售价为
4
4
4,容量为
40
40
40;第三瓶饮料,售价为
5
5
5,容量为
30
30
30;第四瓶饮料,售价为
3
3
3,容量为
20
20
20。
考虑第一件商品状态转移后,
f
[
141
]
∼
f
[
51
]
f[141] \sim f[51]
f[141]∼f[51] 的值为
0
x
3
f
3
f
3
f
3
f
0x3f3f3f3f
0x3f3f3f3f;
f
[
50
]
∼
f
[
1
]
f[50] \sim f[1]
f[50]∼f[1] 的值为
2
2
2。
…
参考代码:
#include <bits/stdc++.h>
using namespace std;
int n, L, c[509], l[509];
int f[2009]; //f[j]:买总容量不低于L的饮料的最小花费
int main()
{
cin >> n >> L; // n种饮料 总容量不低于L
for (int i = 1; i <= n; i++)
cin >> c[i] >> l[i]; // 每一种饮料的售价和容量
memset(f , 0x3f , sizeof(f));
f[0] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = L; j >= 0; j--)
f[j] = min(f[j] , f[max(0 , j - l[i])] + c[i]);
}
if(f[L] != 0x3f3f3f3f)
cout << f[L];
else
cout << "no solution";
return 0;
}