[GESP202309 六级] 小杨买饮料

题目描述

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

小杨的需求有如下几点:

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

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

  3. 小杨勤俭节约,所以在 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,,N1 种饮料:每行两个整数 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 N20;1L100;li100

对于 70 % 70\% 70% 的测试点,保证 l i ≤ 100 l_i \le 100 li100

对于 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 1N500;1L2000;1ci,li106

提交链接

小杨买饮料

解析

搜索的想法(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(nL),这样就能处理大规模数据。

参考代码:

#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[jvolume]+cost)

如果购买 j − v o l u m e j - volume jvolume 毫升的饮料所需的费用已经计算出来,并且这个容量是合法的,那么我们可以用当前饮料来更新 f [ j ] f[j] f[j]

考虑时间复杂度, n n n 最大为 500 500 500 f f f 数组最大能开到 200000 200000 200000 n ∗ l i n * l_i nli 最大为 5 ∗ 1 0 8 5 * 10^8 5108,这个思路只能拿 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zaiyang遇见

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值