貪心法(——模板習題與總結)


摘要

  本文主要講解貪心法的基本思想和實現,怎么運用貪心法,着重講解在編程競賽中的一些典型應用。

  什么是貪心法?

  在編程競賽中的典型應用有哪些?

  例題解析


什么是貪心法?

  貪心法本質上講不是一種真正的算法,而是一種思想,就是解決問題的時候遵循着某種規則,不斷貪心地選取當前最優策略,以達到結果最優的目的。比如硬幣問題,給出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\n");
34         else
35             printf("%d %d\n", 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\n", 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] = '\0';
18         //puts(s); 
19         
20         int a = 0, b = n - 1;
21         while (a <= b) {
22             bool left = false;
23             //比較從左起和從右起的字符串
24             for (int i = 0; a + i <= b; i++) {
25                 if (s[a + i] < s[b - i]) {
26                     left = true;
27                     break;
28                 } else if (s[a + i] > s[b - i]) {
29                     left = false;
30                     break;
31                 }
32             }
33             
34             if (left) putchar(s[a++]);
35             else putchar(s[b--]);
36             cnt++;
37             if(cnt == 80){
38                 printf("\n");
39                 cnt = 0;
40             }
41         }
42         printf("\n");
43     }
44     return 0;
45 }

  POJ 3069 Saruman's Army 典型的點覆蓋問題,貪心的規則是保證能夠覆蓋最左邊的點的情況下,盡量選取一個靠左點,然后跳過它能夠覆蓋的點,如此重復。代碼如下:

 1 /*
 2     給出一個燈能夠照亮的范圍r,給出n個點的位置,問至少需要安裝多少個燈,使得每個點都在燈的范圍內
 3     使用貪心法,找到一個最左邊的位置,加上r,跳過能夠覆蓋的所有點,找到第一個不能覆蓋的點,作為燈,
 4     然后再往右走r,跳過r內的點,計數,直到走完。 
 5 */
 6 #include <cstdio>
 7 #include <algorithm>
 8 using namespace std;
 9 
10 const int maxn = 1010;
11 
12 int a[maxn];
13 int main () 
14 {
15     int r, n;
16     while (scanf("%d%d", &r, &n) == 2 && r + n != -2) {
17         for (int i = 0; i < n; i++) {
18             scanf("%d", &a[i]);
19         }
20         sort (a, a + n);
21         
22         int i = 0, ans = 0;
23         while (i < n) {
24             int s = a[i++];
25             while (i < n && s + r >= a[i]) 
26                 i++;
27             int p = a[i - 1];
28             while (i < n && p + r >= a[i]) 
29                 i++;
30             
31             ans++;
32         }
33         printf ("%d\n", ans);
34     }
35     return 0;
36 } 

  POJ 3253 Fence Repair 該題的解法作為計算法哈夫曼編碼的算法而被熟知,使用了C++ STL 中的優先隊列,實現起來非常簡單。優先隊列自定義優先級的兩種方法如下:

 1 #include <cstdio>
 2 #include <queue>
 3 using namespace std;
 4 typedef long long LL;
 5 priority_queue<int, vector<int>, greater<int> > pq;
 6 int main()
 7 {
 8     int n;
 9     while (scanf("%d", &n) != EOF) {
10         while(!pq.empty())
11             pq.pop();
12         
13         int tmp;
14         for(int i = 0; i < n; i++) {
15             scanf("%d", &tmp);
16             pq.push(tmp);
17         }
18         
19         LL ans = 0;
20         while(pq.size() > 1) {
21             int min1 = pq.top();
22             pq.pop();
23             int min2 = pq.top();
24             pq.pop();
25             
26             ans += min1 + min2;
27             pq.push(min1 + min2);
28         }    
29         printf("%lld\n", ans);
30     }
31     return 0;
32 }
 1 #include <cstdio>
 2 #include <queue>
 3 using namespace std;
 4 typedef long long ll;
 5 struct cmp {
 6     bool operator () (const int &a, const int &b) const {
 7         return a > b;
 8     }
 9 };
10 priority_queue<int, vector<int>, cmp> pq;
11 
12 int main ()
13 {
14     int n;
15     while (scanf("%d", &n) != EOF) {
16         while (!pq.empty())
17             pq.pop();
18         for (int i = 0; i < n; i++) {
19             int tmp;
20             scanf("%d", &tmp);
21             pq.push(tmp);
22         }
23         ll ans = 0;
24         while(pq.size() > 1) {
25             int min1 = pq.top();
26             pq.pop();
27             int min2 = pq.top();
28             pq.pop();
29             
30             ans += min1 + min2;
31             pq.push(min1 + min2);
32         }
33         printf("%lld\n", ans);
34     }
35     return 0;
36 }

  貪心法最大的特點就是和動態規划的相比只顧眼前的最優,熟悉動態規划的話,對貪心法會有更深層的理解,另外求解最短路中的prim算法和Dijkstra算法等都是運用了貪心法的思想。要學會並不難,關鍵是多動手實踐。

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM