摘要
本文主要讲解贪心法的基本思想和实现,怎么运用贪心法,着重讲解在编程竞赛中的一些典型应用。
什么是贪心法?
在编程竞赛中的典型应用有哪些?
例题解析
什么是贪心法?
贪心法本质上讲不是一种真正的算法,而是一种思想,就是解决问题的时候遵循着某种规则,不断贪心地选取当前最优策略,以达到结果最优的目的。比如硬币问题,给出1元、5元、10元、50元、100元的硬币各a、b、c、d、e个,问用这些硬币来支付A元,最少需要多少枚硬币?
很容易想到先用面额大的钱,依次往小即可。这就是贪心法的典型应用,只顾眼前的利益最大化,从而达到总体最优的效果。
在编程竞赛中的典型应用有哪些?
硬币问题,给出硬币的面额和个数,给出需要支付的钱数A,问至少、至多需要多少个硬币?比如HDU 3348 coins
区间问题,给出区间的个数和各个区间的起始和终止,问不能交叉的选取,最多能选取多少个区间?比如HDu 2037 今年暑假不AC
字典序最小问题,给出一个字符串,每次只能从头或者尾取一个字符组成另一个字符串,问字典序最小的字符串是哪个?比如POJ 3617 Best Cow Line
点覆盖问题,给出一个点能够覆盖的范围r和点数以及每个点的位置,问最少需要多少个点使得所有的点都被覆盖? 比如POJ 3069 Saruman's Army
哈夫曼编码问题,给出总的木板长度,需要分割的n个木块及长度,问将这根木板分割成n段的最小代价是多少,其中切割的代价等于被切木板的长度,比如将长度为13的木板切成8和5,所花费的代价是13。比如POJ 3253 Fence Repair
例题解析
HDU 3348 coins 问至少和至多需要多少个硬币,至少容易求,直接贪心先选取面额大的硬币即可,关键是怎么求最多需要多少硬币,有人想到还使用贪心,先选取面额小的硬币,这样是有漏洞的,优先将小的硬币选完之后,可能造成无法凑成A。
这里采用一种反向思维的解法,要想给出的硬币最多,那么手中剩下的硬币就得最少,这样就可以按照之前的方法解题了。只不过要凑的钱数变成了sum-A,计算出的是凑完手中钱最少需要多少个硬币,再用总的硬币数减去它,就可以的到答案了。参考代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int v[5] = {1, 5, 10, 50, 100}; 6 7 int main() 8 { 9 int T; 10 int A, a[5]; 11 scanf("%d", &T); 12 while(T--) { 13 scanf("%d%d%d%d%d%d", &A, &a[0], &a[1], &a[2], &a[3], &a[4]); 14 int mina = 0; 15 int p1 = A; 16 for(int i = 4; i >= 0; i--) { 17 int t = min(p1 / v[i], a[i]); 18 p1 -= t * v[i]; 19 mina += t; 20 } 21 22 int maxa = 0; 23 int sum = a[0] + 5 * a[1] + 10 * a[2] + 50 * a[3] + 100 * a[4]; 24 int p2 = sum - A; 25 for(int i = 4; i >= 0; i--) { 26 int t = min(p2 / v[i], a[i]); 27 p2 -= t * v[i]; 28 maxa += t; 29 } 30 maxa = a[0] + a[1] + a[2] + a[3] + a[4] - maxa; 31 32 if(p1 != 0 || p2 != 0) 33 printf("-1 -1 "); 34 else 35 printf("%d %d ", mina, maxa); 36 } 37 return 0; 38 }
HDu 2037 今年暑假不AC 典型的区间调度问题,先将区间按照结束的早的优先级高的规则排序,然后每次选取结束时间最早的区间。用到了结构体排序,注意迭代的过程。
参考代码如下:
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 const int maxn = 110; 6 struct Node { 7 int s, e; 8 }node[maxn]; 9 bool cmp(Node a, Node b) { 10 if(a.e == b.e) 11 return a.s < b.s; 12 return a.e < b.e; 13 } 14 int main() 15 { 16 int n; 17 while(scanf("%d", &n) == 1 && n) { 18 for(int i = 0; i < n; i++) { 19 scanf("%d%d", &node[i].s, &node[i].e); 20 } 21 sort(node, node + n, cmp); 22 23 int ans = 0, t = 0; 24 for(int i = 0; i < n; i++) { 25 if(t <= node[i].s){ 26 ans++; 27 t = node[i].e; 28 } 29 } 30 printf("%d ", ans); 31 } 32 return 0; 33 }
POJ 3617 Best Cow Line 很容易想到贪心的规则是每次从头或者尾选取较小的字符,不过需要注意的是如果两个字符相等,就需要比较后面的,换句换说就要比较一个串的正和反哪个字典序小,从而得出结论。实现较为巧妙,参考代码:
1 /* 2 给出一个字符串,问每次只能从头或者尾取一个字符,组成最小字典序的字符串 3 每次比较从左起和从右起的字符串,比较出就输出 4 */ 5 #include <cstdio> 6 7 const int maxn = 101000; 8 char s[maxn], rs[maxn]; 9 10 int main() 11 { 12 int n, cnt = 0; 13 while (scanf("%d", &n) != EOF) { 14 for (int i = 0; i < n; i++) { 15 scanf(" %c", &s[i]); 16 } 17 s[n] = '