1.简单贪心
贪心算法是求解一类最优化问题的方法,它总是考虑在当前状态下的局部最优(或较优)策略,来使全局的结果达到最优。
显然如果采用较优而非最优的策略,得到的全局结果也不会是最优的,因此严谨的使用贪心法求解最优化问题需要对采取的策略出进行证明,一般采用反证法与数学归纳法,即假设策略不能导致最优解,然后通过一些列推到来得到矛盾, 以此来证明策略是最优的,最后用数学归纳法保证全局最优。
eg1:PAT-B 1020
思路:
1)总是选择最高价的月饼出售,就可以获得最大的利润,因此对每种月饼,都根据其库存总量个总售价来计算该种月饼的单价,将所有月饼按单价由高到低排序。
2)从单价高的月饼开始枚举,如果该月饼的库存量不足以填补所有需求量,则将该种月饼全部卖出,此时需求量减少该种月饼的库存量大小,收益值增加该种月饼的总售价大小。
如果该种月饼的库存量足够供应需求量,则只需提供需求量大小的月饼,此时收益值增加当前需求量乘以该种月饼的单价,而需求量减少为0。
贪心策略正确性的证明:
假设有两种单价不同的月饼,其单价分别为a和b,如果当前需求量为k,那么两种月饼总收入为aK和bK,而aK<bK显然成立。
AC代码:
//PAT_A 1070
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct mooncake {
double store;
double sell;
double price;
}cake[1010];
bool cmp(mooncake a, mooncake b) {
return a.price > b.price;
}
int main() {
int n;
double D;
(void)scanf("%d%lf", &n, &D);
for (int i = 0; i < n; i++) {
(void)scanf("%lf", &cake[i].store);
}
for (int i = 0; i < n; i++) {
(void)("%lf", &cake[i].sell);
cake[i].price = cake[i].sell / cake[i].store;
}
sort(cake, cake + n, cmp);
double ans = 0;
for (int i = 0; i < n; i++) {
if (cake[i].store <= D) {
D -= cake[i].store;
ans += cake[i].sell;
}
else {
ans += cake[i].price * D;
break;
}
}
printf("%.2f\n", ans);
return 0;
}
eg2:PAT-B 1023
思路:
定义数组读入每一个数字的个数、
先从1-9中选择个数不为0的最小数输出,然后从0-9输出数字,每个数字输出次数为其剩余个数。
贪心策略正确性的证明:
首先,所有的数字都必须参与组合,因此最后结果的位数是确定的,然后,由于最高位不能为0,因此需要从[1,9]中选择最小的数输出(如果存在两个长度相同的数的最高位不同,那么一定是最高位小的数更小)。最后,针对除最高位之外的所有位,也是从高到低优先选择[0,9]中还存在的最小的数输出。
AC代码:
//PAT_B 1023
#include<cstdio>
using namespace std;
int main() {
int count[10];
for (int i = 0; i < 10; i++) {
(void)scanf("%d", &count[i]);
}
for (int i = 1; i < 10; i++) {
if (count[i] > 0) {
printf("%d", i);
count[i]--;
break;
}
}
for (int i = 0; i < 10; i++) {
for (int j = 0; j < count[i]; j++) {
printf("%d", i);
}
}
return 0;
}
2.区间贪心
eg:区间不相交问题:给出N个开区间(x,y),从中选择尽可能多的开区间,使得这些区间之间两两没有交集,如开区间(1,3)(2,4)(3,5)(6,7),最多可以选出3个区间(3,5),(6,7),它们互相没有交集。
1)首先考虑最简单的情况,即开区间I1被开区间I2包含。
此时应当选择I1,因为如果选择I1,就会有更大的空间去容纳其他开区间。
2)接下来把所有的开区间按左端点x从大到小排序,如果去除点区间包含的情况,那么应该有y1>y2>…yn成立。
可以看到I1的右端一定是有一段不与其他区间重叠,如果把它去掉,则I1的左边剩余部分被I2包含,由第一中情况可以知道,这时候选择I1更好。因此对这种情况,总是选择左断点最大的情况。
(同理也可以选择右端点最小的区间的策略)
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 110;
struct Interval {
int x, y;//左右区间断点
}I[maxn];
bool cmp(Interval a, Interval b) {
if (a.x != b.x)return a.x > b.x;//先按区间左断点有大到小排序
else return a.y < b.y;//左端点相同按右端点从小到大排序
}
int main() {
int n;
(void)scanf("%d", &n);
for (int i = 0; i < n; i++) {
(void)scanf("%d %d", &I[i].x, &I[i].y);
}
sort(I, I + n, cmp);
int ans = 1, lastX = I[0].x;//ans记录不相交的区间个数,lastX记录上一个被选中的区间的左断点
for (int i = 1; i < n; i++) {
if (I[i].y <= lastX) {//如果该区间的右端点在lastX的左边,表示两个区间不相交
lastX = I[i].x;//以I[i]作为新选中的区间
ans++;
}
}
printf("%d\n", ans);
return 0;
}
//Input
4
1 3
2 4
3 5
6 7
//Output
3
与这个问题类似的是区间选点问题:给出N个闭区间[x,y],求最少需要确定多少个点,才能使每个闭区间中都至少存在一个点。
例如对闭区间[1,4][2,6][5,7]来说,需要两个点才能保证每个区间都有点,如3,5
策略与区间不相交问题是一致的。
首先,如果I1被I2包含,那么在I1中取点就一定可以保证这个点在I2内,接着把所有区间左断点从大到小排序,显然,每个区间取左端点,就可以让该点覆盖到尽可能的空间。
将区间不相交问题的判断条件if (I[i].y <= lastX)
改为if (I[i].y < lastX)
即可,就是区间端点不能相碰。
贪心算法用来解决一类最优化问题,并希望由局部最优策略推得全局最优结果的算法思想。贪心算法适用的问题一定满足最优子结构性质,即一个问题的最优解可以由它的子问题有效的构造出来。