# Week 13~14:貪心法 Greedy 要點:貪心,要更多、更快、更好的結果 對於任何情況,都選當下最好的選擇,如此便能得到問題的最佳解 並非所有問題都能用greedy 能用greedy 的題目通常具有以下性質: 1. 最佳解會包含子問題的最佳解 2. 最佳解通常可以由當下最好的選擇得到 通常會先定義「貪心法則」,如何選擇才是目前的最佳解,解決小問題 接下來不斷重複用「貪心法則」,找到每個小問題的最佳解,直到解決整個問題 ## Fractional Knapsack Problem 問題:有個背包可以裝M克的物品,現在有n種礦石,每種礦石都有各自的價錢$p_i$和存量$w_i$,每裝進$x_i$克的礦石能得到$p_i$ x $x_i$ 元(須滿足$x_i$≤$w_i$,$\sum_{i=1}^nx_i$≤M),問最多能賺到多少錢? 想法:每種礦石不一定要拿完,可以只拿一部份→簡化為拿M克礦石能賺錢的最大值→從價值最高的開始greedy,直到背包沒有空間 ```cpp= #include <bits/stdc++.h> using namespace std; struct rock { int price; int weight; }a[1005]; bool cmp(rock a,rock b) { return a.price>b.price; } int main() { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n,m; cin>>n>>m; for(int i=0;i<n;i++) cin>>a[i].weight>>a[i].price; sort(a,a+n,cmp); int ans=0; for(int i=0;i<n;i++) { if(m>=a[i].weight) { ans+=a[i].weight*a[i].price; w-=a[i].weight; } else { ans+=a[i].price*m; break; } } cout<<ans<<endl; return 0; } ``` ## 誰先晚餐 問題:有n個人每個人恰點了一份餐,餐點要花$c_i$分製作,$t_i$分食用,請問該如何安排製作餐點的順序才能使所有人都吃完的時間最短? 想法:先從做食用時間長的人的餐點開始greedy,可以在食用時間比較短的人等餐食開始吃,廚師做餐的時間是固定的$\sum_{i=1}^nc_i$。 ```cpp= #include <bits/stdc++.h> using namespace std; struct people { int t,c; }a[1005]; bool cmp(people a,people b) { return a.t>b.t; } int main() { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n,m; cin>>n>>m; for(int i=0;i<n;i++) { cin>>a[i].c>>a[i].t; } sort(a,a+n,cmp); int now=0, ans=0; for(int i=0;i<n;i++) { now+=a[i].c; ans=max(ans,now+a[i].t); } cout<<ans<<endl; return 0; } ``` ## Movie Festival 問題:有n場電影要放映,給每場電影的開始與結束時間,問最多可以看幾場電影?(同時間只能看一場電影,且一定要完整看完) 想法:越早看完就葛以越早開始看下一場電影。 ```cpp= #include <bits/stdc++.h> #define ll long long using namespace std; struct movie { ll l; ll r; }a[2000005]; bool cmp(movie a,movie b) { if(a.r!=b.r) return a.r<b.r; return a.l<b.l; } int main() { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n; cin>>n; for(int i=0;i<n;i++) cin>>a[i].l>>a[i].r; sort(a,a+n,cmp); ll ans=0,now=-1; for(int i=0;i<n;i++) { if(a[i].l>=now) { ans++; now=a[i].r; } } cout<<ans<<endl; return 0; } ``` ## Many task problem 問題:有n個人下了訂單,每筆有各自的完成所需時間$t_i$,問要如何安排訂單順序才能使等待時間總和最短? 想法:所需時間越短的時間要先做→放在越前面,後面等的人就越多。 假設t[i]是第i筆訂單所需時間,答案會是$\sum_{i=1}^n(n-i+1)$ x $t[i]$→讓越小的乘比較多次→放越前面做 ```cpp= #include <bits/stdc++.h> using namespace std; bool cmp(int a,int b) { return a<b; } int main() { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n; while(cin>>n) { for(int i=0;i<n;i++) { cin>>a[i]; } sort(a,a+n,cmp); int now=0,ans=0; for(int i=0;i<n-1;i++) { now+=a[i]; ans+=now; } cout<<ans<<endl; } return 0; } ``` ## Tasks and Deadlines 問題:有n個任務要完成,每個任務都有各自的完成期限$deadline_i$和利潤$profit_i$,問要如何排序才能使每個任務都在期限內完成且利潤最大? 想法:依照利潤由大到小排序,接著遍歷每個任務並確保在期限內完成 ```cpp= #include <bits/stdc++.h> using namespace std; struct task { int job_id; int deadline; int profit; }a[100005]; bool cmp(task a,task b) { return a.profit>b.profit; } void tasksequence(task a[],int num) { sort(a,a+n,cmp); int result[num]; bool s[num]; memset(s,0,sizeof(s)); for(int i=0;i<num;i++) { for(int j=min(num,a[i].deadline)-1;j>=0;j--) { if(s[j]==false) { result[j]==i; s[j]=true; break; } } } for(int i=0;i<num;i++) { if(s[i]==true) { cout<<a[result[i]].job_id<<" "; } } } int main() { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int n; cin>n; for(int i=0;i<n;i++) cin>>a[i].job_id>>a[i].deadline>>a[i].profit; tasksequence(a,num); } ``` ## Many tasks with weight 問題:有n個客人下了一筆訂單,每筆有各自的完成時間$t_i$、重要性$w_i$,價錢$v_i$,訂單的計價方式為:設第i個任務在f分時完成,可以賺到$v_i-f$ x $w_i$,問要如何安排訂單順序以賺最多錢? 想法:$\sum_{i=1}^nv_i$為定值,需要考慮$w_i$,因此$t_i$短或$w_i$大排在前面不一定比較好→讓w/t值越大的排在越前面 證明:根據假設,w/t值越大的會排在越前面,可以找到任兩個訂單w[i]/t[i]>w[i+1]/t[i+1],若交換兩者順序,其他訂單不會受影響,則w[i]延後了t[i+1],w[i+1]提早了t[i],賺到的錢變化為w[i] x t[i+1] - w[i+1] x t[i] >0,賺到的錢變多 ```cpp= #include <bits/stdc++.h> #define ll long long using namespace std; struct task { ll t; ll w; ll v; }a[1005]; bool cmp(task a,task b) { return 1.0*a.w/a.t>1.0*b.w/b.t; } int main() { ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); ll n; cin>>n; for(int i=0;i<n;i++) cin>>a[i].t>>a[i].w>>a[i].v; sort(a,a+n,cmp); ll now=0,ans=0; for(int i=0;i<n;i++) { now+=a[i].t; ans+=a[i].v-now*a[i].w; } cout<<ans<<endl; return 0; } ```