文章目录
-
-
- 贪心算法
-
- 贪心算法思想
- 贪心算法基本思路
- 贪心算法解题步骤
- 贪心算法存在问题
- 一些例子
-
- Selfish Grazing
-
- Cows on a Leash
-
- 纪念品分组
-
- 总结
贪心算法
贪心算法思想
- 贪心算法是指在对问题进行求解时,总是做出当前情况下看起来是最好结果的操作,该结果不一定是整体最优的,而是在某种条件或者某种意义上的局部最优解
- 贪心选择是指所求问题的整体最优解可以通过一系列局部最优解得到,即贪心选择得到
- 当一个问题的最优解包含其子问题的最优解时,成此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解
- 问题的最优子结构形式是该问题可用贪心算法求解的重要特征
- 贪心算法的每一次操作都对结果产生直接影响,贪心算法对每一个子问题的解决方案都做出选择,不能回退
- 贪心算法适用的贪心算法情况很少,一般对一个问题分析是否使用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可以做出简单判断
贪心算法基本思路
- 从问题的某一个初始解出发一步一步进行,根据某个优化测度,每一步确保能获得局部最优解
- 每一步只考虑一个数据,每一步的选取满足局部优化的条件
- 若下一个数据和部分最优解连载一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止
贪心算法解题步骤
- 建立数学模型来描述问题
- 把求解的问题分成若干个子问题
- 对每一子问题进行求解,得到子问题的局部最优解
- 把子问题的局部最优解合成原来问题的一个解
贪心算法存在问题
- 不能保证求得的最后结果是最优解
- 不能用来求最大值最小值问题
- 只能求满足某些约束条件的可行解范围
一些例子
Selfish Grazing
题目
链接:https://ac.nowcoder.com/acm/problem/24867
来源:牛客网
题号:NC24867
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld
Each of Farmer John's N (1 <= N <= 50,000) cows likes to graze in a certain part of the pasture, which can be thought of as a large one-dimeensional number line. Cow i's favorite grazing range starts at location Si and ends at location Ei (1 <= Si < Ei; Si < Ei <= 100,000,000).
Most folks know the cows are quite selfish; no cow wants to share any of its grazing area with another. Thus, two cows i and j can only graze at the same time if either Si >= Ej or Ei <= Sj. FJ would like to know the maximum number of cows that can graze at the same time for a given set of cows and their preferences.
Consider a set of 5 cows with ranges shown below:
... 1 2 3 4 5 6 7 8 9 10 11 12 13 ...
... |----|----|----|----|----|----|----|----|----|----|----|----|----
Cow 1: <===:===> : : :
Cow 2: <========:==============:==============:=============>:
Cow 3: : <====> : : :
Cow 4: : : <========:===> :
Cow 5: : : <==> : :
These ranges represent (2, 4), (1, 12), (4, 5), (7, 10), and (7, 8), respectively.
For a solution, the first, third, and fourth (or fifth) cows can all graze at the same time. If the second cow grazed, no other cows could graze. Also, the fourth and fifth cows cannot graze together, so it is impossible for four or more cows to graze.
* Line 1: A single integer: N
* Lines 2..N+1: Line i+1 contains the two space-separated integers: Si and Ei
* Line 1: A single integer representing the maximum number of cows that can graze at once.
输入
5
2 4
1 12
4 5
7 10
7 8
输出
3
解题思路
- 将算法问题转化为数学问题最终是要求解最多不重复子区间
- 题解思路:
- 题目描述已给出边界条件
- 题目描述得知需要运用贪心的思想
- 首先将数据按照Ei的大小升序排列
- E1 < E2 S1 > S2
[E2, S2]被区间[E1, S1]包含,
故为了之后的数据省空间选择[E2, S2]区间
- E1 < E2 S1 > S2
[E2, S2]被区间[E1, S1]部分包含,
故为了之后的数据省空间选择[E1, S1]区间
- E2 > E1
不存在包含情况,两区间均可以选择
故,包含的情况均选择S较小的靠左情况
- 注意输出的格式要求
代码
/*
题解思路:
1. 题目描述已给出边界条件
2. 题目描述得知需要运用贪心的思想
3. 首先将数据按照Ei的大小升序排列
1. E1 < E2 S1 > S2
[E2, S2]被区间[E1, S1]包含,
故为了之后的数据省空间选择[E2, S2]区间
2. E1 < E2 S1 > S2
[E2, S2]被区间[E1, S1]部分包含,
故为了之后的数据省空间选择[E1, S1]区间
3. E2 > E1
不存在包含情况,两区间均可以选择
故,包含的情况均选择S较小的靠左情况
4. 注意输出的格式要求
*/
# include
# include
# include
# include
using namespace std;
const int N = 50010;
struct Scope {
int begin;
int end;
};
int n;
Scope scope[N];
bool cmp (Scope a, Scope b) {
return a.end < b.end;
}
int main () {
scanf("%d", &n);
for (int i = 0 ; i < n ; i ++) {
scanf("%d%d", &scope[i].begin, &scope[i].end);
}
sort(scope, scope+n, cmp);
int cnt = 1, now = scope[0].end;
for (int i = 1 ; i < n ; i ++) {
if (scope[i].begin >= now) {
cnt ++;
now = scope[i].end;
}
}
printf("%d\n", cnt);
return 0;
}
Cows on a Leash
题目
链接:https://ac.nowcoder.com/acm/problem/25136
来源:牛客网
题号:NC25136
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld
给定如图所示的若干个长条。你可以在某一行的任意两个数之间作一条竖线,从而把这个长条切开,并可能切开其他长条。问至少要切几刀才能把每一根长条都切开。样例如图需要切两刀。
注意:输入文件每行的第一个数表示开始的位置,而第二个数表示长度。
Line 1: A single integer, N(2 <= N <= 32000)
Lines 2..N+1: Each line contains two space-separated positive integers that describe a leash. The first is the location of the leash's stake; the second is the length of the leash.(1 <= length <= 1e7)
Line 1: A single integer that is the minimum number of cuts so that each leash is cut at least once.
输入
7
2 4
4 7
3 3
5 3
9 4
1 5
7 3
输出
2
解题思路
- 切长条
- 因为只要区间有重复,表示这个区间一定可以在切其他区间的同时把这个区间切开,所以就变成了求最大的不相交区间数量
- 按照结束时间来排序
- 上一个越早结束,下一个开始的时间就越早,选择的空间就越大
- 如果有重复的也没有关系
代码
/*
解题思路:
1. 题目描述已给出边界条件
2. 题目描述得该题可以按照贪心策略解题
3. 将所有输入数据进行排序 左端记为first,右端记为second
1. 整体按照second的大小进行升序排序
2. 若存在second相等情况,比较first
x.first < y.first
4. 排序结束后,按照排序结果遍历所有数据,flag表示划线位置
1. x.first > flag 表名x,y无重合项,需要重新划线
5. 注意根据当前遍历情况更新flag与划线数量cnt
6. 注意数据输入输出格式
*/
# include
# include
# include
# include
# define max 32005
using namespace std;
struct node {
int first, second;
}num[max];
bool cmp (node x, node y) {
if (x.second == y.second) {
return x.first < y.first;
}
return x.second < y.second;
}
int main() {
int n;
cin >> n;
for (int i = 0 ; i < n ; i ++) {
int len;
cin >> num[i].first >> len;
num[i].second = num[i].first + len;
}
sort(num, num+n, cmp);
int cnt = 0, flag = 0;
for (int i = 0 ; i < n ; i ++) {
if (flag <= num[i].first) {
cnt ++;
flag = num[i].second;
}
}
cout << cnt << endl;
return 0;
}
纪念品分组
题目
链接:https://ac.nowcoder.com/acm/problem/16640
来源:牛客网
题号:NC16640
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。
你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。
第 1 行包括一个整数 w,为每组纪念品价格之和的上限。
第 2 行为一个整数n,表示购来的纪念品的总件数。
第 3 ~ n+2 行每行包含一个正整数 pi ( 5 ≤ pi ≤ w ) ,表示所对应纪念品的价格。
包含一个整数,即最少的分组数目。
输入
100
9
90
20
20
30
50
60
70
80
90
输出
6
备注:
50%的数据满足:1 ≤ n ≤ 15
100%的数据满足:1 ≤ n ≤ 30000, 80 ≤ w ≤ 200
解题思路
- 很多题解不完全使用贪心一个策略,贪心策略本身也有复杂度,需考虑除贪心的其余复杂度
- 如果枚举最小的礼品,要去找是否有礼品和它组队,而且枚举下一个礼品时还要判断是否组队了,比较麻烦
- 如果枚举最大的礼品,升序排序,如果最前面的礼品可以和这个礼品放一组,那么去掉这两个礼品,继续枚举下一个礼品
代码
/*
解题思路:
1. 题目描述已给出边界条件
2. 题目描述得将数据进行有序化后相加比较就可
3. 有序化后使用两个指针从两头同步提取数据相加比较
4. 根据题目要求记录满足条件组合数
5. 注意数据输出格式
*/
# include
# include
# include
# include
using namespace std;
const int N = 30010;
// quick_sort
void quick_sort (int num[], int low, int high) {
int i, j, temp;
int tmp = num[low];
i = low;
j = high;
if (i > j) {
return;
}
while (i != j) {
while (num[j] >= tmp && j > i) {
j --;
}
while (num[i] <= tmp && j > i) {
i ++;
}
if (j > i) {
temp = num[j];
num[j] = num[i];
num[i] = temp;
}
}
num[low] = num[i];
num[i] = tmp;
quick_sort(num, low, i-1);
quick_sort(num, j+1, high);
}
int main () {
int n, max;
int num[N];
scanf("%d", &max);
scanf("%d", &n);
for (int i = 0 ; i < n ; i ++) {
scanf("%d", &num[i]);
}
quick_sort(num, 0, n-1);
int left = 0;
int right = n-1;
int cnt = 0;
while (left <= right) {
if (num[left] + num[right] <= max) {
left ++;
right --;
cnt ++;
} else {
right --;
cnt ++;
}
}
printf ("%d", cnt);
return 0;
}
总结
- 贪心策略本身就是在一定条件下获得的局部最优解,所以在确定是否使用贪心算法前需要经过算法模型的思考与模拟;
- 可以用贪心策略的同时也要兼顾其余方法的复杂度,包括时间复杂度与空间复杂度