【GESP】C++一级真题练习(202309)luogu-B3863,买文具

<think>我们被要求搜索关于GESP202309六级题目“小杨饮料”的题目内容和解题思路。 搜索词:GESP202309 六级 小杨饮料 根据搜索结果,我们了解到以下信息: 题目内容: 小杨有 $n$ 家饮料店可供选择,第 $i$ 家店提供 $a_i$ 毫升饮料,价格为 $b_i$ 元。小杨需要至少 $m$ 毫升饮料,他可以选择在任意多家店购。求小杨需要花费的最小金额。如果无法满足需求,则输出 -1。 输入格式: 第一行两个整数 $n$ 和 $m$。 接下来 $n$ 行,每行两个整数 $a_i$ 和 $b_i$。 输出格式: 一行一个整数,表示最小花费。如果不能满足,输出 -1。 数据范围: $1 \leq n \leq 100$,$1 \leq m \leq 2000$,$1 \leq a_i, b_i \leq 2000$。 解题思路: 这是一个典型的背包问题,但需要至少满足 $m$ 毫升,而不是不超过容量。因此,我们可以将其转化为一个“至少”背包问题(也称为完全背包的最小花费问题,但这里每个物品只有一个)。 状态定义:设 $dp[j]$ 表示获得至少 $j$ 毫升饮料所需的最小花费。 状态转移方程:对于每个饮料店 $i$,有 $a_i$ 毫升和 $b_i$ 元,我们可以考虑或不。 如果不,则状态不变。 如果,则状态从 $dp[\max(0, j - a_i)]$ 转移而来,因为当 $j - a_i < 0$ 时,我们只需要这一家店就可以满足至少 $j$ 毫升(此时花费为 $b_i$)。 因此状态转移方程为: $dp[j] = \min(dp[j], dp[\max(0, j - a_i)] + b_i)$ 初始化: $dp[0] = 0$,表示获得0毫升需要0元。 对于 $j>0$,初始化为一个很大的数(比如无穷大),表示初始状态不可达。 最后,我们要求的是 $dp[m]$,如果 $dp[m]$ 仍然是无穷大,则输出 -1。 注意:由于每个物品只能使用一次(每家店最多一次),所以这是一个01背包问题,而不是完全背包。因此,在循环时,第二层循环(容量循环)应该从大到小遍历,以避免重复使用。 但是,题目中并没有说每家店只能一次,但输入只给了每家店的信息(每家店只有一种饮料,提供固定毫升和价格),并且小杨可以选择在任意多家店购,每家店只能一次(因为每家店只提供一份)。因此,这实际上是一个01背包问题。 另外,由于要求至少 $m$ 毫升,所以我们的 $dp$ 数组需要开 $m+1$ 以上,但注意,由于转移时可能超过 $m$,但超过 $m$ 的部分我们都可以视为 $m$(因为超过 $m$ 的部分我们不需要,所以我们可以将状态只计算到 $m$,或者将超过 $m$ 的部分也记录下来,但为了节省空间,我们可以将状态只计算到 $m$,因为当 $j>m$ 时,我们都可以用 $dp[m]$ 来代表,因为题目要求至少 $m$,所以超过 $m$ 的都可以归为 $m$。但实际上,在状态转移中,我们对于 $j$ 从 $m$ 到 $0$ 进行遍历时,当 $j$ 超过 $m$ 时,我们并不需要计算,因为我们的目标就是 $m$。然而,在状态转移中,我们可能会用到超过 $m$ 的状态吗?不会,因为我们的 $j$ 最大只到 $m$,然后我们取 $\max(0, j-a_i)$,所以不会超过 $m$。但是,由于 $a_i$ 可能大于 $j$,此时 $j-a_i$ 为负数,我们取0,所以不会出现负下标。 因此,我们设计一个 $dp$ 数组,长度为 $m+1$,初始化 $dp[0]=0$,其他为无穷大(一个很大的数,比如0x3f3f3f3f,且保证不会溢出)。 然后,对于每个物品 $i$,从后往前更新 $dp$ 数组(01背包倒序): for i in range(n): for j from m down to 0: dp[j] = min(dp[j], dp[max(0, j - a_i)] + b_i) 最后,如果 $dp[m] >= \text{inf}$(即初始化的无穷大),则输出 -1,否则输出 $dp[m]$。 但是,注意:题目要求是“至少” $m$,所以当我们有超过 $m$ 的饮料时,花费可能更少吗?不会,因为我们的状态定义是至少 $j$ 毫升的最小花费,所以当 $j$ 超过 $m$ 时,我们并不需要计算超过 $m$ 的状态,因为我们的目标就是 $m$。但是,在转移过程中,我们可能会更新超过 $m$ 的状态吗?不会,因为我们的 $j$ 最大只循环到 $m$,而 $a_i$ 最大为2000,$m$ 最大为2000,所以 $j - a_i$ 最小为0,不会出现负数。而且,我们只更新到 $j=0$ 到 $j=m$。 然而,有一种情况:如果某家店提供的饮料量很大,那么当我们计算 $j$ 从 $m$ 到0的时候,对于 $j$ 小于 $a_i$ 的情况,我们就会用 $dp[0]+b_i$ 来更新 $dp[j]$。这符合题意:这家店,至少获得 $j$ 毫升(因为 $a_i$ 已经大于等于 $j$,所以这一家店就满足了)。 因此,代码实现如下: 步骤: 1. 读入 n 和 m。 2. 初始化 dp 数组,长度为 m+1,dp[0]=0,其余为一个大数(比如0x3f3f3f3f)。 3. 循环读入 n 个物品的 a_i 和 b_i。 4. 对于每个物品,倒序遍历 j 从 m 到 0: dp[j] = min(dp[j], dp[max(0, j - a_i)] + b_i) 5. 如果 dp[m] 等于初始化的无穷大,则输出 -1,否则输出 dp[m]。 但是,注意:题目中要求的是“至少”m毫升,所以我们的dp[m]就是答案。 然而,我们考虑一下边界:如果m=0,那么需要0元,但题目中m>=1,所以不用考虑。 另外,由于我们只计算到m,所以可能有些情况是实际获得的饮料量超过m,但花费更少?不会,因为我们的状态转移方程中,对于超过m的部分我们并不计算,但注意,在更新时,如果j-a_i小于0,我们就用0来更新,这样实际上就相当于我们只用了这一家店就满足了(因为这一家店的饮料量已经大于等于j了)。而且,我们最后要求的是至少m,所以dp[m]就是答案。 但是,我们也可以考虑:也许我们不需要刚好达到m,超过m也可以,但花费可能比dp[m]小?不会,因为dp[m]表示至少m的最小花费,所以即使超过m,花费也不会小于dp[m]。因为dp[m]已经包含了所有能覆盖m的情况(包括超过m的)。例如,我们了一个饮料店,它有a_i>=m,那么花费就是b_i,而dp[m]就会被更新为b_i(如果b_i比之前的值小)。 因此,上述解法正确。 但是,我们需要注意:在倒序遍历时,j的范围应该是从m到0吗?实际上,在01背包中,我们通常从大到小遍历,以避免重复使用。这里同样,每个物品只能使用一次,所以倒序。 代码示例(C++): ```cpp #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; int main() { int n, m; cin >> n >> m; vector<int> dp(m+1, INF); dp[0] = 0; for (int i = 0; i < n; i++) { int a, b; cin >> a >> b; for (int j = m; j >= 0; j--) { int prev = max(0, j - a); dp[j] = min(dp[j], dp[prev] + b); } } if (dp[m] == INF) { cout << -1 << endl; } else { cout << dp[m] << endl; } return 0; } ``` 然而,我们注意到数据范围:$m$ 最大为2000,$n$ 最大为100,所以这个双重循环(100*2000)是20万次,可以接受。 但是,还有一种情况:可能有些店提供的饮料量很大,但价格很高,我们需要比较。 因此,我们按照上述思路编写代码即可。 注意:初始化dp[0]=0,因为0毫升需要0元,其他初始化为无穷大(表示不可达)。 最后,输出dp[m](如果dp[m]是无穷大则输出-1)。 这就是题目的解题思路。 现在,我们根据要求生成回答,并附上相关问题。</think>### GESP202309六级题目:小杨饮料 #### 题目内容 小杨需要购至少 $m$ 毫升饮料,有 $n$ 家饮料店可选。每家店 $i$ 提供 $a_i$ 毫升饮料,售价 $b_i$ 元。可在任意多家店购,求最小花费。若无法满足需求,输出 -1。 **输入格式**: - 第一行:$n$(店铺数)和 $m$(最少需求毫升数) - 接下来 $n$ 行:每行 $a_i$(毫升数)和 $b_i$(价格) **输出格式**: 最小花费或 -1 **数据范围**: - $1 \leq n \leq 100$ - $1 \leq m \leq 2000$ - $1 \leq a_i, b_i \leq 2000$ #### 解题思路(动态规划) **状态定义** 设 $dp[j]$ 表示获得至少 $j$ 毫升饮料的最小花费。 **状态转移方程** 对于每家店 $(a_i, b_i)$ 和容量 $j$: $$dp[j] = \min\left(dp[j],\ dp[\max(0,\ j - a_i)] + b_i\right)$$ 其中 $\max(0,\ j - a_i)$ 处理超额购($a_i > j$ 时视为满足需求)。 **初始化** - $dp[0] = 0$(0毫升需0元) - $dp[j] = +\infty$($j > 0$,初始设为极大值如 `0x3f3f3f3f`) **算法流程** 1. 初始化 $dp$ 数组 2. 遍历每家店铺 3. 倒序枚举 $j$ 从 $m$ 到 $0$(避免重复计算) 4. 按转移方程更新 $dp[j]$ 5. 若 $dp[m]$ 未更新则输出 -1 **代码实现** ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; int main() { int n, m; cin >> n >> m; vector<int> dp(m + 1, INF); dp[0] = 0; for (int i = 0; i < n; i++) { int a, b; cin >> a >> b; for (int j = m; j >= 0; j--) { int prev = max(0, j - a); dp[j] = min(dp[j], dp[prev] + b); } } cout << (dp[m] == INF ? -1 : dp[m]) << endl; return 0; } ``` **关键点** - **倒序遍历 $j$**:确保每家店仅使用一次(01背包特性) - **边界处理**:$\max(0,\ j - a_i)$ 避免负索引 - **复杂度**:$O(n \times m)$($n \leq 100,\ m \leq 2000$,满足时限)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值