# 貪心法 by: hush --- ## 概念 ---- ### 貪心演算法(Greedy algorithm) - 透過每次都選當下最優解,達到全局最優解 - **不是**所有問題都可以用貪心法解決 - 常用在最優解問題 - 遇到類似問題就大膽地猜 ---- - 要寫greedy證明很重要,通常會利用反證法或是歸納法,不過比賽中的證明不需要太嚴謹 - ||不在場證明|| - ||prove by AC|| --- ## 經典題目 --- ### 找錢問題 ---- * [csdc267](https://csdc.tw/problem/267/) ---- - 因為對所有幣值數對都是倍數關係,用目前最大面額的鈔票支付必定最佳 - 例如: $9784=$ $9\times 1000+1\times 500+2\times 100$ $+1\times 50+3\times 10+0\times 5+4\times 1$ 共$20$個貨幣 ---- AC code: ```cpp= #include<bits/stdc++.h> #define int long long #define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(0); using namespace std; int mny[]={500, 100, 50, 10, 5, 1}; signed main() { //colinorz; int n,sum=0; cin >> n; n=1000-(n%1000); for (int i=0;i<6;i++) { sum+=n/mny[i]; n%=mny[i]; } cout << sum << '\n'; } ``` ---- :::danger 注意:若有不是倍數關係則不一定成立 ::: 這時要使用dp去解決 ---- 練習題: * [csdc348](https://csdc.tw/problem/348/) ---- AC code: ```cpp= #include<bits/stdc++.h> #define int long long #define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(0); using namespace std; int pap[]={10000,5000,2000,1000},coin[]={500,100,50,10,5,1}; signed main() { //colinorz; int n,ansp=0,ansc=0; cin >> n; n<<=2; for (int i=0;i<4;i++) ansp+=n/pap[i],n%=pap[i]; for (int i=0;i<6;i++) ansc+=n/coin[i],n%=coin[i]; cout << ansp <<' '<< ansc << '\n'; } ``` --- ### 堆疊問題 ---- - 例題:[csdc238](https://csdc.tw/problem/238/) - 題意: - 有$5$本書要疊成一疊,每次取一本書要花費那本上面所有書重總和(不含自己),取完馬上放回 - 第$i$本書重$w_i$要取$t_i$次 - 求最低搬書總花費 ---- - 題解: - $\because$ 放越上面被算到越多次 - 重量輕、拿取次數多應放上面 - $\therefore$ 合理推測$\frac{重量(w_i)}{次數(t_i)}$越小要放越上面 符合最小總花費數列的順序必滿足$\frac{w_i}{t_i}\le\frac{w_j}{t_j}, i<j$ (位置越小越上面) 結論:照著上述規則排序後算出花費即可 另解:只有5本書所以可直接枚舉排列 ---- - 小技巧: $\frac{w_i}{t_i}\le\frac{w_j}{t_j}$可以寫成$w_i\cdot t_j\le w_j\cdot t_i$ 避免除法產生的浮點數運算 ---- AC code: ```cpp= #include <bits/stdc++.h> #define int long long #define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(0); using namespace std; struct book { int w,t; }; bool cmp(book i,book j) { return i.w*j.t<j.w*i.t; } book a[6]; signed main() { //colinorz; for (int i=0;i<5;i++) cin >> a[i].t; for (int i=0;i<5;i++) cin >> a[i].w; sort(a,a+5,cmp); int sum=0,wsum=a[0].w; for (int i=1;i<5;i++) { sum+=wsum*a[i].t; wsum+=a[i].w; } cout << sum << '\n'; } ``` ---- - 練習題: [APCS 1710-4.物品堆疊](https://zerojudge.tw/ShowProblem?problemid=c471) - 題解: 原理同上題(但不能枚舉了XD) ---- AC code: ```cpp= #include <bits/stdc++.h> #define int long long #define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(0); using namespace std; struct book { int w,t; }; bool cmp(book i,book j) { return i.w*j.t<j.w*i.t; } vector<book> a(100005); signed main() { //colinorz; int n; cin >> n; for (int i=0;i<n;i++) cin >> a[i].t; for (int i=0;i<n;i++) cin >> a[i].w; sort(a.begin(),a.begin()+n,cmp); int sum=0,wsum=a[0].w; for (int i=1;i<n;i++) { sum+=wsum*a[i].t; wsum+=a[i].w; } cout << sum << '\n'; } ``` --- ### 切棍子問題 ---- - [CSEC1161](https://cses.fi/problemset/task/1161/) - 題意: 你有一條長度為$x$的棍子,你要將他切成$n$隻指定長度的棍子,長度加總起來恰為$x$。 拿起一根棍子切成兩段花費的費力是那根棍子的長度,求達成題目要求最少要花費多少力氣? ---- - 題解: 可以把題目想成要合併這$n$隻棍子,那麼越長的應該要越慢合併(減少合併次數)。 1.每次從棍子堆取出最短的兩隻 2.合併成新的棍子再丟回去棍子堆 3.重複1. 2.直到所有棍子合併完成 (要快速維護最小值可使用**heap**) ---- AC code ```cpp= #include<bits/stdc++.h> #define int long long #define itn int #define endl '\n' #define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(0); using namespace std; priority_queue<int,vector<int>,greater<int>> pq; signed main() { //colinorz; int n,x,ans=0; cin >> x >> n; for (int i=0;i<n;i++) { int d; cin >> d; pq.emplace(d); } for (int i=0;i<n-1;i++) { int tmp=pq.top(); pq.pop(); tmp+=pq.top(); pq.pop(); ans+=tmp; pq.emplace(tmp); } cout << ans << endl; } ``` --- ### 排程問題 ---- - [APCS 2301-4.機器出租](https://zerojudge.tw/ShowProblem?problemid=j608) - 題意: 有$N$個活動,舉辦每個活動都要租借一台機器,若要舉辦第$i$個活動,就需要在時間段$[ l_i, r_i]$租借一台機器。 已知$N$個活動的需要使用的時間段,並且有$K$台機器可以開放租借,且一台機器一次只能借給一個活動,請問最多可以辦幾場活動? ---- - 題解: - 將活動照結束時間排序 - 維護所有機器上個活動結束時間 - 遍歷所有活動,從所有$<$活動開始時間的機器結束時間中選擇最大的,並使用機器(浪費最少) - 若無符合的機器則跳過 ---- AC code: ```cpp= #include <bits/stdc++.h> #define int long long #define pii pair<int,int> #define fi first #define se second #define colinorz cin.tie(0);cout.tie(0);ios::sync_with_stdio(0); using namespace std; bool cmp(pii a,pii b) { if (a.se!=b.se) return a.se<b.se; return a.fi<b.fi; } vector<pii> a(100005); vector<int> mch(105,-1); signed main() { //colinorz; int n,k,ans=0; cin >> n >> k; for (int i=0;i<n;i++) cin >> a[i].fi; for (int i=0;i<n;i++) cin >> a[i].se; sort(a.begin(),a.begin()+n,cmp); for (int i=0;i<n;i++) { int slc=-1; for (int j=0;j<k;j++) if ( mch[j]<a[i].fi && (slc==-1||mch[j]>mch[slc]) ) slc=j; if (slc==-1) continue; ans++; mch[slc]=a[i].se; } cout << ans << endl; } ``` --- ## 更多練習題 ---- - [csdc205](https://csdc.tw/problem/205/) - [csdc192](https://csdc.tw/problem/192/) - [csdc316](https://csdc.tw/problem/316/) - [zj_m603](https://zerojudge.tw/ShowProblem?problemid=m603) - [zj_b966](https://zerojudge.tw/ShowProblem?problemid=b966) --- # 謝謝大家
{"description":"by: hush","title":"貪心法","contributors":"[{\"id\":\"b49547c8-0e7f-46d0-99b2-8a45dfee8e90\",\"add\":7228,\"del\":1663}]"}
    25 views