1.贪心是什么
贪心算法(Greedy Algorithm)是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好的策略。虽然这种策略并不保证一定能得到全局最优解,但在许多情况下,它能提供近似最优解,而且计算效率高。贪心算法通常适用于那些具有“最优子结构”和“贪心选择性质”的问题,比如霍夫曼编码、最小生成树等。
2.基本思路
- 建立数学模型:首先需要将实际问题抽象成数学模型,明确问题的目标和约束条件。
- 分解子问题:将原问题分解成多个子问题,这些子问题的解决方案有助于构建整个问题的解决方案。
- 局部最优解:对于每一个子问题,贪心算法会做出在当前状态下看似最优的选择。这种选择通常是基于某种度量标准或规则来确定的。
- 合成全局解:通过一系列局部最优解的组合,期望能够得到全局最优解。重要的是,一旦做出了选择,就不会再回头更改这个选择,即便后续发现有更好的选择也是如此。
3.操作步骤
步骤:
- 问题建模:明确问题的边界条件、目标函数以及约束条件。
- 子问题划分:将问题分解为更小的子问题,这些子问题应该具有与原问题相同的结构。
- 贪心选择:对于每一个子问题,做出一个看起来最优的选择。这一步是贪心算法的核心。
- 构造解:将子问题的解组合起来,形成完整问题的一个解。
- 验证解:检查构造的解是否满足所有约束条件,并符合目标函数的要求。
例子:
假设我们有一个简单的例子,比如选择一组硬币来支付一个给定金额的情况,我们的目标是以 最少数量的硬币完成支付。
- 问题建模:设定硬币面值(例如1分、5分、10分、25分)和需要支付的总金额。
- 子问题划分:将支付金额逐步减少,每次减少一个硬币面值的金额。支付金额递减的过程,比如从37分开始,减去25分,然后减去10分,再减去1分。
- 贪心选择:每次选择面值最大的硬币,只要不超过剩余的支付金额。每次选择硬币的过程:先选25分,再选10分,最后选1分。
- 构造解:将选择的所有硬币加起来,构成最终的支付方案。最终选择的硬币组合,即25分+10分+1分。
- 验证解:确认所选硬币的总和等于需要支付的金额,并且硬币数量最少。验证过程,确认37分已完全支付并且只用了3枚硬币。
4.代码模板(以硬币找零问题为例)
python模板:
def min_coins(coins, amount):
# 对硬币面值进行降序排序
coins.sort(reverse=True)
# 初始化结果变量
result = 0
# 遍历每个硬币面值
for coin in coins:
# 计算当前硬币可以使用的次数
count = amount // coin
# 更新结果
result += count
# 减去已经支付的金额
amount -= count * coin
# 如果金额变为0,则返回结果
if amount == 0:
return result
# 如果无法支付,则返回-1
return -1
# 示例
coins = [1, 5, 10, 25]
amount = 37
print(min_coins(coins, amount)) # 输出: 3
c++模板:
#include <iostream>
#include <vector>
#include <algorithm>
int minCoins(std::vector<int>& coins, int amount) {
// 对硬币面值进行降序排序
std::sort(coins.begin(), coins.end(), std::greater<int>());
int result = 0;
// 遍历每个硬币面值
for (int coin : coins) {
// 计算当前硬币可以使用的次数
int count = amount / coin;
// 更新结果
result += count;
// 减去已经支付的金额
amount -= count * coin;
// 如果金额变为0,则返回结果
if (amount == 0) {
return result;
}
}
// 如果无法支付,则返回-1
return -1;
}
int main() {
std::vector<int> coins = {1, 5, 10, 25};
int amount = 37;
std::cout << minCoins(coins, amount) << std::endl; // 输出: 3
return 0;
}
5.经典例题
p1223排队接水
题目描述
有 n 个人在一个水龙头前排队接水,假如每个人接水的时间为 Ti,请编程找出这 n 个人排队的一
种顺序,使得 n 个人的平均等待时间最小。
输入格式
第一行为一个整数 n。
第二行 n 个整数,第 i 个整数 Ti 表示第 i 个人的接水时间 Ti。
输出格式
输出文件有两行,第一行为一种平均时间最短的排队顺序;第二行为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)。
样例 #1
样例输入 #1
10
56 12 1 99 1000 234 33 55 99 812
样例输出 #1
3 2 7 8 1 4 9 6 10 5
291.90
提示
1 < n < 1000,1 < ti <10^6,不保证 ti 不重复。
代码:
#include <bits/stdc++.h>
using namespace std;
// 定义结构体
struct ss {
int num; // 任务编号
int time; // 任务时间
} a[1001]; // 结构体数组,最多存储1001个任务
int n; // 任务总数
double ans = 0.0; // 存储总的等待时间
// 自定义比较函数,用于排序
bool cmp(const ss &a, const ss &b) {
return a.time < b.time; // 按照时间从小到大排序
}
int main() {
// 读入任务数量
scanf("%d", &n);
// 读入每个任务的时间戳
for (int i = 0; i < n; i++) {
scanf("%d", &a[i].time);
a[i].num = i + 1; // 设置任务编号
}
// 根据时间戳排序
sort(a, a + n, cmp);
// 计算平均等待时间和输出任务编号
for (int i = 0; i < n - 1; i++) {
ans += a[i].time * (n - i - 1); // 计算每个任务的等待时间并累加
printf("%d ", a[i].num); // 输出当前任务的编号
}
printf("%d\n", a[n - 1].num); // 输出最后一个任务的编号
// 输出平均等待时间,保留两位小数
printf("%.2lf\n", ans / n);
return 0;
}
p1803凌乱的yyy / 线段覆盖
题目背景
快 noip 了,yyy 很紧张!
题目描述
现在各大 oj 上有 n 个比赛,每个比赛的开始、结束的时间点是知道的。
yyy 认为,参加越多的比赛,noip 就能考的越好(假的)。
所以,他想知道他最多能参加几个比赛。
由于 yyy 是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加 2 个及以上的比赛。
输入格式
第一行是一个整数 n,接下来 n 行每行是 2 个整数 ai,bi(ai<bi),表示比赛开始、结束的时间。
输出格式
一个整数最多参加的比赛数目。
样例 #1
样例输入 #1
3
0 2
2 4
1 3
样例输出 #1
2
提示
- 对于 20% 的数据,n < 10;
- 对于 50% 的数据,n < 10^3;
- 对于 70% 的数据,n < 10^5;
- 对于 100% 的数据,1< n < 10^6,0 < ai < bi <10^6。
代码:
#include <bits/stdc++.h>
using namespace std;
// 定义结构体
struct node {
int st; // 开始时间
int ed; // 结束时间
// 自定义比较运算符
bool operator<(const node &tmp) const {
if (ed == tmp.ed) {
return st < tmp.st; // 如果结束时间相同,则按照开始时间排序
} else {
return ed < tmp.ed; // 否则按照结束时间排序
}
}
} a[1000010]; // 结构体数组,最多存储1000010个时间段
int main() {
int n, x, y, s, cnt;
cnt = 0;
s = 0;
// 读入时间段数量
cin >> n;
// 读入每个时间段的开始时间和结束时间
for (int i = 0; i < n; i++) {
cin >> a[i].st >> a[i].ed;
}
// 按照结束时间排序
sort(a, a + n);
// 计算最少覆盖点
for (int i = 0; i < n; i++) {
if (a[i].st >= s) {
s = a[i].ed; // 更新覆盖点的位置
cnt++; // 覆盖点数量加1
}
}
// 输出最少覆盖点的数量
cout << cnt << endl;
return 0;
}
p1090[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n-1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 1 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 3 种果子,数目依次为 1 , 2 , 9 。可以先将 1 、 2 堆合并,新堆数目为 3 ,耗费体力为 3 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12 ,耗费体力为 12 。所以多多总共耗费体力 =3+12=15 。可以证明 15 为最小的体力耗费值。
输入格式
共两行。
第一行是一个整数 n(1<= n <= 10000) ,表示果子的种类数。
第二行包含 n 个整数,用空格分隔,第 i 个整数 ai(1<=ai<= 20000) 是第 i 种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2^31 。
样例 #1
样例输入 #1
3
1 2 9
样例输出 #1
15
提示
对于 30% 的数据,保证有 n < 1000:
对于 50% 的数据,保证有 n < 5000;
对于全部的数据,保证有 n < 10000。
代码:
#include <bits/stdc++.h>
using namespace std;
long long n, a[100010];
int main() {
// 读入数组长度
cin >> n;
// 读入数组元素
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
// 将数组从小到大排序
sort(a + 1, a + n + 1);
long long ans = 0;
// 主循环,进行合并操作
while (true) {
int i = 1;
// 找到第一个非零元素
while (a[i] == 0) {
i++;
}
// 如果已经只剩下一个非零元素,退出循环
if (i == n) {
break;
} else {
// 合并当前元素和下一个元素
a[i] += a[i + 1];
// 累加合并代价
ans += a[i];
// 移动数组元素,删除已合并的元素
for (int l = i + 1; l < n; l++) {
a[l] = a[l + 1];
}
// 数组长度减一
n--;
// 再次排序,确保数组有序
for (int l = i; l < n; l++) {
if (a[l] > a[l + 1]) {
swap(a[l], a[l + 1]);
}
}
}
}
// 输出最终答案
cout << ans << endl;
return 0;
}
标签:输出,果子,硬币,int,coins,amount,算法,例题,贪心
From: https://blog.csdn.net/Alex_Fufu/article/details/142302573