题目链接:02徒步旅行中的补给问题
问题描述
小R正在计划一次从地点A到地点B的徒步旅行,总路程需要 N
天。为了在旅途中保持充足的能量,小R每天必须消耗1份食物。幸运的是,小R在路途中每天都会经过一个补给站,可以购买食物进行补充。然而,每个补给站的食物每份的价格可能不同,并且小R最多只能同时携带 K
份食物。
现在,小R希望在保证每天都有食物的前提下,以最小的花费完成这次徒步旅行。你能帮助小R计算出最低的花费是多少吗?
输入输出
-
输入:
n
:总天数。k
:小R最多能携带的食物份数。data
:一个长度为n
的数组,表示每天经过的补给站的食物价格。
-
输出:
- 一个整数,表示完成旅行的最低花费。
示例
样例1:
输入:
n = 5 ,k = 2 ,data = [1, 2, 3, 3, 2]
输出:9
样例2:
输入:
n = 6 ,k = 3 ,data = [4, 1, 5, 2, 1, 3]
输出:9
样例3:
输入:
n = 4 ,k = 1 ,data = [3, 2, 4, 1]
输出:10
解题思路
贪心算法简介
贪心算法是一种在每一步选择中都采取在当前状态下最优的选择,以期望通过一系列局部最优的选择达到全局最优的算法。贪心算法通常适用于满足以下两个条件的问题:
- 最优子结构:问题的最优解包含其子问题的最优解。
- 贪心选择性质:通过局部最优选择可以得到全局最优解。
贪心算法的关键在于如何证明每一步的局部最优选择能够最终达到全局最优解。
贪心算法在本题中的应用
在本题中,我们可以使用贪心算法来解决。具体步骤如下:
- 初始化:设置当前携带的食物数量为0,总花费为0。
- 遍历每一天:
- 如果当前携带的食物数量不足1份,则需要购买食物。
- 购买食物时,选择当前及未来
K
天内最便宜的价格进行购买,直到满足当天的食物需求。
- 更新状态:购买食物后,更新当前携带的食物数量和总花费。
代码实现
以下是使用贪心算法的C++代码示例:
#include <iostream>
#include <vector>
#include <algorithm> // for min_element
int solution(int n, int k, std::vector<int> data) {
int current_food = 0; // 当前携带的食物数量
int total_cost = 0; // 总花费
for (int i = 0; i < n; ++i) {
// 如果当前食物不足1份,需要购买食物
while (current_food < 1) {
// 找到当前及未来K天内最便宜的价格
int min_price = data[i];
for (int j = i; j < std::min(i + k, n); ++j) {
min_price = std::min(min_price, data[j]);
}
// 购买食物,更新总花费和当前食物数量
total_cost += min_price;
current_food += 1;
}
// 每天消耗1份食物
current_food -= 1;
}
return total_cost;
}
int main() {
// 测试样例
std::cout << solution(5, 2, {1, 2, 3, 3, 2}) << std::endl; // 输出: 9
std::cout << solution(6, 3, {4, 1, 5, 2, 1, 3}) << std::endl; // 输出: 9
std::cout << solution(4, 1, {3, 2, 4, 1}) << std::endl; // 输出: 10
return 0;
}
代码解释
-
初始化:
current_food
:当前携带的食物数量。total_cost
:总花费。
-
遍历每一天:
- 如果
current_food
不足1份,则需要购买食物。 - 在当前及未来
K
天内找到最便宜的价格进行购买。 - 更新
total_cost
和current_food
。
- 如果
-
每天消耗1份食物:
- 更新
current_food
。
- 更新
贪心算法的经典示例
1. 活动选择问题
问题描述:给定一组活动,每个活动有一个开始时间和结束时间,选择尽可能多的互不重叠的活动。
贪心策略:每次选择结束时间最早的活动,这样可以为后续活动留下更多的时间。
代码示例:
#include <iostream>
#include <vector>
#include <algorithm>
struct Activity {
int start, end;
};
bool compare(Activity a, Activity b) {
return a.end < b.end;
}
int maxActivities(std::vector<Activity> activities) {
std::sort(activities.begin(), activities.end(), compare);
int count = 1;
int last_end = activities[0].end;
for (int i = 1; i < activities.size(); ++i) {
if (activities[i].start >= last_end) {
count++;
last_end = activities[i].end;
}
}
return count;
}
int main() {
std::vector<Activity> activities = {{1, 2}, {3, 4}, {0, 6}, {5, 7}, {8, 9}, {5, 9}};
std::cout << maxActivities(activities) << std::endl; // 输出: 4
return 0;
}
2. 找零问题
问题描述:给定一些面值不同的硬币,用最少的硬币数量凑出指定的金额。
贪心策略:每次选择面值最大的硬币,直到凑出指定的金额。
代码示例:
#include <iostream>
#include <vector>
int minCoins(std::vector<int> coins, int amount) {
std::sort(coins.rbegin(), coins.rend());
int count = 0;
for (int coin : coins) {
while (amount >= coin) {
amount -= coin;
count++;
}
}
return count;
}
int main() {
std::vector<int> coins = {1, 5, 10, 25};
int amount = 49;
std::cout << minCoins(coins, amount) << std::endl; // 输出: 7
return 0;
}
贪心算法的更多示例
1. 分数背包问题
问题描述:给定一组物品,每个物品有重量和价值,选择一些物品放入一个容量有限的背包中,使得背包中物品的总价值最大。每个物品可以选择部分放入背包。
贪心策略:每次选择单位重量价值最高的物品放入背包,直到背包满为止。
2. 最小生成树问题
问题描述:给定一个带权无向图,找到一个生成树,使得树中所有边的权重之和最小。
贪心策略:
- Kruskal算法:每次选择权重最小的边,并确保不形成环。
- Prim算法:从一个顶点开始,每次选择与当前生成树连接的权重最小的边,并将其加入生成树。
3. 霍夫曼编码
问题描述:给定一组字符及其出现的频率,设计一种编码方式,使得编码后的字符串长度最短。
贪心策略:每次选择频率最小的两个字符,合并它们并生成一个新的节点,重复此过程直到所有字符都被合并成一棵树。
4. 区间调度问题
问题描述:给定一组区间,每个区间有开始时间和结束时间,选择尽可能多的互不重叠的区间。
贪心策略:每次选择结束时间最早的区间,这样可以为后续区间留下更多的时间。
5. 硬币找零问题
问题描述:给定一些面值不同的硬币,用最少的硬币数量凑出指定的金额。
贪心策略:每次选择面值最大的硬币,直到凑出指定的金额。
6. 任务调度问题
问题描述:给定一组任务,每个任务有开始时间和结束时间,以及一个优先级,选择一组任务使得总优先级最大,并且任务之间不重叠。
贪心策略:每次选择优先级最高的任务,并确保不与已选择的任务重叠。
7. 最小延迟调度问题
问题描述:给定一组任务,每个任务有处理时间和截止时间,选择一种调度方式使得任务的最大延迟最小。
贪心策略:按照截止时间排序,优先处理截止时间早的任务。
8. 最大子数组问题
问题描述:给定一个数组,找到一个连续的子数组,使得子数组的和最大。
贪心策略:从左到右遍历数组,维护当前子数组的和,如果和变为负数,则从当前位置重新开始计算子数组。
总结
贪心算法是一种简单且高效的算法,适用于满足最优子结构和贪心选择性质的问题。通过每一步的局部最优选择,贪心算法可以快速找到全局最优解。在本题中,我们通过贪心策略,选择当前及未来 K
天内最便宜的食物价格进行购买,从而确保每天的食物需求都能以最小的花费满足。