算法竞赛入门与进阶 (三)贪心
貪心算法:
在對問題求解的時(shí)候,總是做出在當(dāng)前看來最好的選擇,
也就是說不從整體上進(jìn)行考慮,它所做出的僅僅是在某種意義上的局部最優(yōu)解
是否是全優(yōu)解,需要證明。
若用貪心算法求解某問題的整體最優(yōu)解,
必須證明貪心思想在該問題的應(yīng)用結(jié)果就是最優(yōu)解
貪心:每次都選看起來最好的!
例一:
有n個(gè)人在一個(gè)水龍頭前排隊(duì)接水,假如每個(gè)人接水的時(shí)間為Ti,
請編程找出這n個(gè)人排隊(duì)的一種順序,使得n個(gè)人的平均等待時(shí)間最小。
貪心策略:
第1個(gè)人的等待時(shí)間為0,第二個(gè)人的等待的時(shí)間為t[1],第三個(gè)人的等待時(shí)間為t[1]+t[2]
第n個(gè)人的等待時(shí)間為前n-1個(gè)人接水的時(shí)間之和,所以將n個(gè)人的接水時(shí)間從小到大排序
這就是n個(gè)人排隊(duì)的順序,使得n個(gè)人的平均等待時(shí)間最小
例二:
銀行排隊(duì)
時(shí)間限制:?1 Sec??內(nèi)存限制:?128 MB提交:?299??解決:?115
[提交][狀態(tài)][討論版]
題目描述
有n個(gè)人將要排隊(duì)在銀行辦理業(yè)務(wù),已知每個(gè)人辦理業(yè)務(wù)所花費(fèi)的時(shí)間,分別是t1,t2,t3...tn?,F(xiàn)在,你可以重新安排他們的排隊(duì)順序,目的是為了使所有人從排隊(duì)開始到業(yè)務(wù)辦理完成花費(fèi)的時(shí)間總和最少?,F(xiàn)在你只需要告訴我,安排好后所有人花費(fèi)的時(shí)間總和最少是多少?輸入
本題有多組測試數(shù)據(jù)。每組數(shù)據(jù)第一行給出一個(gè)正整數(shù)n,代表有n個(gè)人排隊(duì),其中n<=100。接下來一行有n個(gè)正整數(shù),分別代表這每個(gè)人辦理業(yè)務(wù)所需要花費(fèi)的時(shí)間。輸出
對每個(gè)測試用例,在每一行里輸出所有人花費(fèi)時(shí)間的總和最少的值。樣例輸入
5 3 1 2 2 1 10 2 1 3 1 3 4 5 8 7 4樣例輸出
22 147#include<stdio.h> #include<algorithm> using namespace std;int tim[101]; int main() {int N;while(scanf("%d",&N)!=EOF){for(int i=0;i<N;i++)scanf("%d",&tim[i]);sort(tim,tim+N);int ans=0,sum;for(int i=0;i<N;i++){sum=0;for(int j=0;j<=i;j++){sum +=tim[j];}ans+=sum;} printf("%d\n",ans);}return 0; }
例三:
| 水題 | ||||||
| ||||||
| Description | ||||||
因?yàn)槭怯嘘P(guān)于接水的問題,便簡稱為水題了(。 N個(gè)人排隊(duì)在M個(gè)出水口前接水,第i個(gè)人接水需時(shí)為t[i], 請問接水的最短用時(shí)是多少? | ||||||
| Input | ||||||
第一行一個(gè)整數(shù)?T?,代表有?T?組數(shù)據(jù)。 每組數(shù)據(jù) 第一行兩個(gè)整數(shù)?N(<=100000) , M(<=10000)?代表有?N?個(gè)人?M?個(gè)出水口。 第二行N個(gè)整數(shù),第i個(gè)數(shù)字t[i](<=10000)代表第i個(gè)人接水用時(shí)t[i]。 | ||||||
| Output | ||||||
| 對于每組數(shù)據(jù)輸出一個(gè)整數(shù),代表所需的最少接水時(shí)間。 | ||||||
| Sample Input | ||||||
2 5 3 1 2 3 4 5 6 3 1 2 3 3 4 5 | ||||||
| Sample Output | ||||||
5 6 | ||||||
| Hint | ||||||
| 小橋流水嘩啦啦,我和小島去偷瓜~。 | ||||||
| Source | ||||||
| "誠德軟件杯"哈爾濱理工大學(xué)第四屆ACM程序設(shè)計(jì)團(tuán)隊(duì)賽 |
點(diǎn)擊打開鏈接
貪心 + 二叉推
將所有人從大到小排序,依次插入 M 個(gè)出水口, 每次要插入到用時(shí)最少的出水口,最后輸出 M個(gè)出水口中用時(shí)最多的
#include<stdio.h> #include<vector> #include<iostream> #include<queue> #include<algorithm> using namespace std;int cmp(int a,int b) {return a>b; } int tim[101000]; int main() {int T,N,M;cin>>T;while(T--){cin>>N>>M;priority_queue<int,vector<int>,greater<int> >pq;//優(yōu)先隊(duì)列,從小到大入隊(duì)列//將 M 個(gè)出水口用時(shí)初始化為 0for(int i=0;i<M;i++)pq.push(0);for(int i=0;i<N;i++)cin>>tim[i];sort(tim,tim+N,cmp);//從大到小排序for(int i=0;i<N;i++){int temp=pq.top();//每次取出時(shí)間最小的出水口pq.pop();//出隊(duì)列temp+=tim[i];//將第 i 個(gè)人插入到目前時(shí)間最小的出水口 pq.push(temp);} int ans=-1;//遍歷循環(huán),找出最大值 while(!pq.empty()){if(ans<pq.top())ans=pq.top();pq.pop(); }printf("%d\n",ans);}return 0; }例四:點(diǎn)擊打開鏈接
合并果子
現(xiàn)在有n堆果子,第i堆有ai個(gè)果子?,F(xiàn)在要把這些果子合并成一堆,每次合并的代價(jià)是兩堆果子的總果子數(shù)。求合并所有果子的最小代價(jià)。
Input第一行包含一個(gè)整數(shù)T(T<=50),表示數(shù)據(jù)組數(shù)。
每組數(shù)據(jù)第一行包含一個(gè)整數(shù)n(2<=n<=1000),表示果子的堆數(shù)。
第二行包含n個(gè)正整數(shù)ai(ai<=100),表示每堆果子的果子數(shù)。
每組數(shù)據(jù)僅一行,表示最小合并代價(jià)。
Sample Input 2 4 1 2 3 4 5 3 5 2 1 4Sample Output 19 33優(yōu)先隊(duì)列
#include<iostream> #include<queue> //#include<bits/stdc++.h> using namespace std; const int MAX=1e4+5; int n; int main() {ios::sync_with_stdio(false);int t;cin>>t;while(t--){int n,x;cin>>n;priority_queue<int,vector<int>,greater<int> >q;for(int i=0;i<n;i++){cin>>x;q.push(x);}long long sum=0;while(q.size()>1){int min1=q.top();q.pop();int min2=q.top();q.pop();sum+=(min1+min2);q.push(min1+min2);}cout<<sum<<endl;} return 0;}STL堆算法
#include<iostream> #include<vector> #include<algorithm> using namespace std; int main() {int t;cin>>t;while(t--){int n;cin>>n;vector<int> v;while(n--){int x;cin>>x;v.push_back(x);}long long sum=0;make_heap(v.begin(),v.end(),greater<int>());while(v.size()>1){int min1=v.front();pop_heap(v.begin(),v.end(),greater<int>());v.pop_back();int min2=v.front();pop_heap(v.begin(),v.end(),greater<int>());v.pop_back();//cout<<min1<<" "<<min2<<endl;sum+=(min1+min2);v.push_back(min1+min2);push_heap(v.begin(),v.end(),greater<int>());}cout<<sum<<endl;}return 0; }例5:
n個(gè)正整數(shù),聯(lián)接成一排,組成一個(gè)最小(最大)的多位整數(shù)
描述:設(shè)有n個(gè)正整數(shù),將它們聯(lián)接成一排,組成一個(gè)最小(最大)的多位整數(shù)。
程序輸入:n個(gè)數(shù)程序輸出:聯(lián)接成的多位數(shù)例如:n=2時(shí),2個(gè)整數(shù)32、321連接成的最小整數(shù)為:32132
n=3時(shí),3個(gè)整數(shù)13,312,343,連成的最大整數(shù)為34331213。
n=4時(shí),4個(gè)整數(shù)7,13,4,246連接成的最大整數(shù)為7424613。
n=4時(shí),4個(gè)整數(shù)55、31、312、33 聯(lián)接成的最小整數(shù)為:312313355
思路:兩個(gè)方向:a.先組合,后排序,b.先排序,后組合,但是要注意:A=’321’,B=’32’,
按照標(biāo)準(zhǔn)的字符串比較規(guī)則因?yàn)锳>B,所以A+B > B+A ,而實(shí)際上’32132’ < ’32321’。?
所以,自定義一種字符串的比較規(guī)則:即如果A+B>B+A,則我們認(rèn)為A>B。
#include<bits/stdc++.h> using namespace std; bool cmp(string a,string b) {return a+b>b+a; } int main() {ios::sync_with_stdio(false);string s;vector<string> v;int n;cin>>n;for(int i=0;i<n;i++){cin>>s;v.push_back(s);}sort(v.begin(),v.end(),cmp);for(int i=0;i<v.size();i++)cout<<v[i];cout<<endl;return 0;}例6
事件序列問題?
已知N個(gè)事件的發(fā)生時(shí)刻和結(jié)束時(shí)刻(見下表,表中事件已按結(jié)束時(shí)刻升序排序)。
一些在時(shí)間上沒有重疊的事件,可以構(gòu)成一個(gè)事件序列,如事件{2,8,10}。
事件序列包含的事件數(shù)目,稱為該事件序列的長度。請編程找出一個(gè)最長的事件序列。
算法分析:
用begin[i],end[i]表示事件i的開始時(shí)刻和結(jié)束時(shí)刻,則問題轉(zhuǎn)化為求 a1<a2<a3<...<an 滿足:
begin[a1]<end[a1] <= begin[a2]<end[a2] <= begin[an]<end[an]
hdu 2037
#include <stdio.h> #include <algorithm> using namespace std; struct node { int t1;//電視開始時(shí)間 int t2;//電視的結(jié)束時(shí)間 }a[105]; int cmp(node u,node v) //對節(jié)目按照結(jié)束時(shí)間從小到大排序, //如果結(jié)束的時(shí)間相同,則按照開始的 {//時(shí)間從大到小的排序! if(u.t2==v.t2)//為什么要將開始的時(shí)間從大到小排序呢?//是因?yàn)槿绻Y(jié)束時(shí)間相同的話,開始的 //越遲,看節(jié)目的時(shí)間越短,你就能盡可能的多看電視! return u.t1>v.t1;return u.t2<v.t2;//例如:2-3,3-4,2-4,你肯定會(huì)看2-3,3-4的兩個(gè)電視,而不看2-4這個(gè)電視 } int main() { int n,i,j,k,t; while(scanf("%d",&n)&&n) { for(i=0;i<n;i++)//有n個(gè)開始和結(jié)束時(shí)間,將時(shí)間輸入 scanf("%d%d",&a[i].t1,&a[i].t2); sort(a,a+n,cmp);//對時(shí)間進(jìn)行排序 for(i=1,t=a[0].t2,k=1;i<n;i++)//如果開始 的時(shí)間比他這個(gè)結(jié)束的時(shí)間遲,則就k++ { if(a[i].t1>=t)//說明這個(gè)電視你能夠看 { t=a[i].t2; k++; } } printf("%d\n",k);//k代表能看的電視的個(gè)數(shù)! } return 0; }例7:
HDU 4221 貪心題意:
給n個(gè)活動(dòng),每個(gè)活動(dòng)需要一段時(shí)間C來完成,并且有一個(gè)截止時(shí)間D,當(dāng)完成時(shí)間t大于截止時(shí)間完成時(shí),會(huì)扣除t-D分,
讓你找出如何使自己所扣分的最大值最小。
注意:求得是沒有按時(shí)完成的任務(wù)罰金最多的那個(gè)!
思路:按照截止時(shí)間從小到大來排序,從第一天開始做任務(wù),每個(gè)任務(wù)的完成時(shí)間減去截止時(shí)間相對來說最少
#include<iostream> #include<algorithm> using namespace std; const int MAX=1e6+5; struct node{int c,d; }a[MAX]; bool cmp(node a,node b) {return a.d<b.d; } int main() {ios::sync_with_stdio(0);int t,T=1;cin>>t;while(t--){int n;cin>>n;for(int i=0;i<n;i++)cin>>a[i].c>>a[i].d;sort(a,a+n,cmp);long long ans=0,time=0;for(int i=0;i<n;i++){time+=a[i].c;if(ans<time-a[i].d){ ans=time-a[i].d;}}printf("Case %d: %I64d\n",T++,ans);}return 0; }
例8:
NOIP2012 國王游戲 題解
描述
恰逢H國國慶,國王邀請n位大臣來玩一個(gè)有獎(jiǎng)游戲。首先,他讓每個(gè)大臣在左、右手上面分別寫下一個(gè)整數(shù),國王自己也在左、右手上各寫一個(gè)整數(shù)。然后,讓這n位大臣排成一排,國王站在隊(duì)伍的最前面。排好隊(duì)后,所有的大臣都會(huì)獲得國王獎(jiǎng)賞的若干金幣,每位大臣獲得的金幣數(shù)分別是:排在該大臣前面的所有人的左手上的數(shù)的乘積除以他自己右手上的數(shù),然后向下取整得到的結(jié)果。?
國王不希望某一個(gè)大臣獲得特別多的獎(jiǎng)賞,所以他想請你幫他重新安排一下隊(duì)伍的順序,使得獲得獎(jiǎng)賞最多的大臣,所獲獎(jiǎng)賞盡可能的少。注意,國王的位置始終在隊(duì)伍的最前面。
格式
輸入格式
第一行包含一個(gè)整數(shù)n,表示大臣的人數(shù)。?
第二行包含兩個(gè)整數(shù)a和b,之間用一個(gè)空格隔開,分別表示國王左手和右手上的整數(shù)。接下來n行,每行包含兩個(gè)整數(shù)a和b,之間用一個(gè)空格隔開,分別表示每個(gè)大臣左手和右手上的整數(shù)。
輸出格式
輸出只有一行,包含一個(gè)整數(shù),表示重新排列后的隊(duì)伍中獲獎(jiǎng)賞最多的大臣所獲得的金幣數(shù)。
樣例1
樣例輸入1[復(fù)制]
3 1 1 2 3 7 4 4 6樣例輸出1[復(fù)制]
2限制
每個(gè)測試點(diǎn)1s
提示
對于20%的數(shù)據(jù),有1≤ n≤ 10,0 < a、b < 8;?
對于40%的數(shù)據(jù),有1≤ n≤20,0 < a、b < 8;?
對于60%的數(shù)據(jù),有1≤ n≤100;?
對于60%的數(shù)據(jù),保證答案不超過10^9;?
對于100%的數(shù)據(jù),有1 ≤ n ≤1,000,0 < a、b < 10000。
【題解】
? 一開始看著題覺得是二分答案(最大值的最小值),后來發(fā)現(xiàn)不滿足單調(diào)性
? 再后來發(fā)現(xiàn)可以用貪心做:只需把大臣按照左手*右手升序排序即可
? 證明:
? ?很顯然前面的大臣位置隨便調(diào)換對后面的大臣并沒有影響
? 那么假設(shè)現(xiàn)在已經(jīng)排了i-1個(gè)大臣,p=a[1]*a[2]*a[3]*……*a[i-1];
? 第i個(gè)大臣的錢w[i]=p/b[i],第i+1個(gè)大臣的錢w[i+1]=p*a[i]/b[i+1]
? 若i+1大臣在i大臣前面
??第i個(gè)大臣的錢w[i]=p*a[i+1]/b[i],第i+1個(gè)大臣的錢w[i+1]=p/b[i+1]
? 顯然p*a[i+1]/b[i]>p/b[i] ?&& ?p*a[i]/b[i+1]>p/b[i+1]
? 所以兩個(gè)里面的最大值在p*a[i+1]/b[i] 和 p*a[i]/b[i+1] 之間 ?
? 若p*a[i+1]/b[i] > p*a[i]/b[i+1](這樣就是i+1大臣排在i后面最大值會(huì)更小) 則 a[i+1]*b[i+1]>a[i]*b[i]
? 所以如果要讓大臣得到的錢的最大值最小就要保證兩個(gè)相鄰的大臣,前面的左手*右手<后面的左手*右手
總結(jié)
以上是生活随笔為你收集整理的算法竞赛入门与进阶 (三)贪心的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: poj 1797 Dijkstra算法
- 下一篇: greaterT()和lessT()