一、贪心算法的特点:
1、贪心选择:
所谓贪心选择是指应用同一规则,将原问题变为一个相似的但规模更小的子问题,而后的每一步都是当前看似最佳的选择,且这种选择只依赖于已做出的选择,不依赖未做出的选择。
2、最优子结构:
执行算法时,每一次得到的结果虽然都是当前问题的最优解(即局部最优解),但只有满足全局最优解包含局部最优解是,才能保证最终得到的结果是最优解。
二、几个贪心例子:
1、最优装载问题
给n个物品,第i个物品重量为wi,选择尽量多的物品,使得总重量不超过C。
【思路】
由于只关心物品的数量,所以装重的没有装轻的划算,只需把所有物品按重量从小到大排序,以此选择每个物品,知道装不下为止。它只顾眼前,却能得到全局最优解。
2、部分背包问题
有n个物品,第i个物品的重量为wi,价值为vi,在总重量不超过C的情况下,让总价值尽量高,每一个物品可以只取走一部分,价值和重量按比例计算。
【思路】
贪心策略:先选出性价比最高的。
注意:由于每个物品可以只选出一部分,因此一定可以让总重量恰好为C(或者所有物品全选,总重量还不足C),而且除了最后一个以外,所有物品要么不选,要么全部选。
3、乘船问题
有n个人,第i个人重量为wi。每艘船的载重量均为C,最多可乘两个人。求用最少的船装在所有人的方案。
【思路】
贪心策略:最轻的人和最重的人配对。
程序实现:我们只需用两个指针i和j分别表示当前考虑的最轻的人和最重的人。每次先将指针j往左移动,直到i和j可以共乘一艘船,然后将指针i往右移,指针j往左移动。
三、贪心算法的经典应用
1、选择不相交区间问题
给定n个开区间(ai,bi),选择尽量多个区间,使得这些区间两两没有公共点。
【思路】
首先,按照结束时间 b1 <= b2 …… <= bn 的顺序排序,依次考虑各个活动,如果没有和已经选择的活动冲突,就选,否则就不选。
【正确性】
假设 bj < bi 且(aj,bj) 、(ai,bi) 分别与之前的活动不冲突,当前选(aj,bj),若(aj,bj)与(ai,bi)不冲突,则还可以选择(ai,bi),答案个数加一,若(aj,bj)与(ai,bi) 冲突,因为 bj < bi ,所以 (aj,bj)对以后的影响更小。
【例题】:活动安排
题目链接:传送门
题目描述
输入
接下来的n行,每行两个整数si和fi。
输出
样例输入
4
1 3
4 6
2 5
1 7
样例输出
2

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 10005; 4 typedef struct Node { 5 int S,F; 6 }Node ; 7 Node a[N]; 8 bool cmp ( Node u , Node v ){ 9 if( u.F == v.F ){ 10 return u.S < v.S ; 11 } 12 return u.F < v.F ; 13 } 14 int n ; 15 int main () { 16 scanf("%d",&n); 17 for ( int i = 0 ; i < n ; i++ ){ 18 scanf("%d%d",&a[i].S, &a[i].F ); 19 } 20 sort ( a , a+n ,cmp ); 21 int ans = 1 ; 22 int F = a[0].F; 23 for ( int i = 1 ; i < n ; i++ ){ 24 if( F <= a[i].S ){ 25 F = a[i].F ; 26 ans ++ ; 27 } 28 } 29 printf("%d\n",ans ); 30 return 0; 31 }
2、区间选点问题
给定n个闭区间[ai,bi],在数轴上选尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)
【思路】
贪心策略:取最后一个
首先按区间的结束位置从小到大排序。然后从区间1到区间n进行选择;对于当前区间,若集合中的数不能覆盖它,则将区间末尾的数加入集合。
【例题】 种树
题目描述
写一个程序计算最少要种树的数量。
输入
第二行包含H,房子的数目(0<H≤5000);
下面的H行描述居民们的需要:B E T,0<B≤E≤30000,T≤E-B+1。
输出
样例输入
9
4
1 4 2
4 6 2
8 9 2
3 5 2
样例输出
5
【题解】:
种树要种得少,就要使一棵树给多个区间使用,这样,尽量在重叠区间种树即可,而重叠位置一定是在区间尾部。处理问题时,先按所有区间的结束位置排序,之后依次处理每个区间,先在第一个区间尾部种满足要求的树,对下一个区间,看差多少颗就在该区间尾部种多少。
【算法步骤】:
1、先按结束位置快速排序。
2、对每个区间依次处理。
a、从前往后扫描这个区间,统计已选点的个数
b、若已选点的个数超过了要求的点数,则continue
c、否则从该区间由后向前扫描,添加缺少的覆盖点。
3、输出ans

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 3e5+100; 4 typedef struct Node { 5 int b,e,t; 6 bool operator < (const Node &rhs) const { 7 if( e == rhs.e ){ 8 return b < rhs.b; 9 } 10 return e < rhs.e ; 11 } 12 }Node ; 13 Node a[N]; 14 int used[N]; 15 int n , h ; 16 int main () 17 { 18 scanf("%d%d",&h,&n); 19 for(int i=0;i<n;i++){ 20 scanf("%d%d%d",&a[i].b,&a[i].e,&a[i].t); 21 } 22 sort ( a , a+n ); 23 for(int i=0;i<n;i++){ 24 int k = 0 ; 25 for(int j=a[i].b ; j<=a[i].e;j++){ 26 if ( used[j] ){ 27 k ++ ; 28 } 29 } 30 if( k >= a[i].t ){ 31 continue; 32 } 33 for(int j=a[i].e ; j>=a[i].b ; j--){ 34 if( used[j] == 0 ){ 35 used[j] = 1 ; 36 k++; 37 if( k==a[i].t)break; 38 } 39 } 40 } 41 int ans = 0 ; 42 for(int i=1;i<=h;i++){ 43 if( used[i] ) 44 ans ++ ; 45 } 46 printf("%d\n",ans); 47 return 0; 48 }
3、区间覆盖问题
给n个闭区间 [ ai , bi ] ,选择尽量少的区覆盖一条指定的线段区间 [s,t]
【思路】
贪心策略:在某个时刻的s,找一个满足 a[i]<=s 的 b[i] 最大值即可
将所有的区间按左端点从小到大排序,依次处理每个区间。每次选择覆盖点s的区间中右端点坐标最大的一个,并将s更新为该区间的右端点坐标,知道选择的区间已包含了t为止。
【例题3】喷水装置
题目描述
请问:如果要同时浇灌整块草坪,最少需要打开多少个喷头?

输入
第一行一个整数T表示数据组数;
每组数据的第一行是整数n、L和W(n≤15000);
接下来的n行,每行包含两个整数,给出一个喷头的位置和浇灌半径(上面的示意图是样例输入第一组数据所描述的情况)。
输出
样例输入
3
8 20 2
5 3
4 1
1 2
7 2
10 2
13 3
16 2
19 4
3 10 1
3 5
9 3
6 1
3 10 1
5 3
1 1
9 1
样例输出
6
2
-1
【题解】:
1、读入数据,并计算S[i].a = x - sqrt ( r*r - (w*w)/4 )
S[i].b = x + sqrt( r*r - (w*w)/4 )
2、按S[i].a 进行从小到大快排
3、从左往右依次处理每个区间

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5+10; 4 typedef struct Node { 5 double s,t; 6 bool operator < ( const Node &rhs ) const { 7 return s < rhs.s ; 8 } 9 }Node; 10 Node a[N]; 11 int main() 12 { 13 int T ,n , L ; 14 double W; 15 scanf("%d",&T); 16 while(T--){ 17 scanf("%d%d%lf",&n,&L,&W); 18 int cnt = 0 ; 19 for(int i=0,x,r;i<n;i++){ 20 scanf("%d%d",&x,&r); 21 if( r <= W/2 ) continue; 22 a[cnt].s = x - sqrt( r*r - W/2 * W/2 ); 23 a[cnt].t = x + sqrt( r*r - W/2 * W/2 ); 24 cnt ++ ; 25 } 26 sort(a,a+cnt); 27 double t = 0 ; 28 int ans = 0 , flag = 1 , i = 0 ; 29 while ( t < L ){ 30 ans ++ ; 31 double s = t ; 32 for ( ; a[i].s <= s && i < cnt ;i++ ){ 33 if( t < a[i].t ) t = a[i].t ; 34 } 35 if ( t==s && s<L ){ 36 printf("-1\n"); 37 flag = 0 ; 38 break; 39 } 40 } 41 if ( flag ){ 42 printf("%d\n",ans); 43 } 44 } 45 return 0; 46 }
4、流水作业调度问题
【问题描述】
有n个作业要在两台机器M1和M2组成的流水线上完成加工。每个作业i都必须先花时间ai在M1上加工,然后花时间bi在M2上加工。
确定n个作业的加工顺序,使得从作业1在机器M1上加工开始到作业n在机器M2上加工为止所用的总时间最短。
【思路】:
直观上,最优调度一定让M1没有空闲,M2的空闲时间尽量短。
Johnson算法:设N1为a<b的作业集合,N2为a>=b的作业集合,将N1的作业按a非减序排序,N2中的作业按照b的非增序排序,则N1作业接N2作业构成最优排序。
算法的程序易实现,时间复杂度为O(nlogn),正确性需要证明.
【例题4】加工生产调度
题目描述
已知:部件i在A、B机器上的加工时间分别为ai,bi。
问:如何安排n个工件的加工顺序,才能使得总加工时间最短?
输入
第2行n个整数,表示这n个产品在A车间加工各自所要的时间(都是整数);
第3行n个整数,表示这n个产品在B车间加工各自所要的时间(都是整数)。
输出
样例输入
5
3 5 8 7 10
6 2 1 4 9
样例输出
34
【贪心策略】
要使机器总的空闲时间最短,就要把在A机器上加工时间最短的部件最先加工,这样使得B机器能在最短的空闲时间内开始加工;把在B机器上加工时间最短的部件放在最后加工,这样使得A机器用最短时间等待B机器完工。

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e4+100; 4 typedef struct Node { 5 int a,b,id; 6 }Node ; 7 Node N1[N],N2[N]; 8 int a[N],b[N],ans[N],n; 9 bool cmp1 ( Node u,Node v ){ 10 return u.a < v.a ; 11 } 12 bool cmp2 ( Node u,Node v ){ 13 return u.b > v.b ; 14 } 15 int main() 16 { 17 scanf("%d",&n); 18 int cnt1 = 0 , cnt2 = 0 ; 19 for(int i=0;i<n;i++){ 20 scanf("%d",&a[i]); 21 } 22 for(int i=0;i<n;i++){ 23 scanf("%d",&b[i]); 24 if( a[i] <= b[i] ){ 25 N1[cnt1].a = a[i] ; 26 N1[cnt1].b = b[i] ; 27 N1[cnt1].id = i+1 ; 28 cnt1 ++ ; 29 }else{ 30 N2[cnt2].a = a[i] ; 31 N2[cnt2].b = b[i] ; 32 N2[cnt2].id = i+1 ; 33 cnt2 ++ ; 34 } 35 } 36 sort ( N1 , N1 + cnt1 , cmp1 ); 37 sort ( N2 , N2 + cnt2 , cmp2 ); 38 int A = 0 , B = 0 , cnt = 0 ; 39 for( int i = 0 ; i < cnt1 ; i++ ){ 40 ans[cnt++] = N1[i].id; 41 } 42 for( int i = 0 ; i < cnt2 ; i++ ){ 43 ans[cnt++] = N2[i].id; 44 } 45 for( int i = 0 ; i < cnt1 ; i++ ){ 46 A += N1[i].a; 47 if ( A > B ) B = A ; 48 B += N1[i].b; 49 } 50 for( int i = 0 ; i < cnt2 ; i++ ){ 51 A += N2[i].a; 52 if ( A > B ) B = A ; 53 B += N2[i].b; 54 } 55 printf("%d\n",B); 56 57 for(int i=0;i<cnt;i++){ 58 printf("%d%c",ans[i],i==cnt-1?'\n':' '); 59 } 60 return 0; 61 }
5、带限期和罚款的单位时间任务调度
【问题描述】
有n个任务,每个都需要1个时间单位执行,任务i的截止时间di ( 1<= di <= n ) 表示要求任务i在时间di结束时必须完成,误时惩罚wi表示若任务i未在时间di结束之前完成,将导致wi的罚款。
【思路】
要使罚款最少,我们显然应尽量完成w[i]值较大的任务。
此时,我们可以将任务按w[i]从大到小进行排序,然后按照排好的顺序依次对任务进行安排。安排的规则为:使处理任务i的时间既在d[i]之内,又尽量靠后;如果d[i]之内的时间都已经排满,就放弃处理此项任务。
【实现方法】
1、先按照罚款数额从大到小快排。
2、顺序处理每个任务,若能安排,则找一个最晚时间;否则放在最后的空位上。
【例题5】智力大冲浪
Problem description
小伟报名参加中央电视台的智力大冲浪节目。本次挑战赛吸引了众多参赛者,主持人为了表彰大家的勇气,先奖励每个参赛者m元。先不要太高兴!因为这些钱还不一定都是你的?!接下来主持人宣布了比赛规则:
首先,比赛时间分为n个时段(n≤500),它又给出了很多小游戏,每个小游戏都必须在规定期限ti前完成(1≤ti≤n)。如果一个游戏没能在规定期限前完成,则要从奖励费m元中扣去一部分钱wi,wi为自然数,不同的游戏扣去的钱是不一样的。当然,每个游戏本身都很简单,保证每个参赛者都能在一个时段内完成,而且都必须从整时段开始。主持人只是想考考每个参赛者如何安排组织自己做游戏的顺序。作为参赛者,小伟很想赢得冠军,当然更想赢取最多的钱!注意:比赛绝对不会让参赛者赔钱!
Input format
共4行。
第1行为m,表示一开始奖励给每位参赛者的钱;
第2行为n,表示有n个小游戏;
第3行有n个数,分别表示游戏1到n的规定完成期限;
第4行有n个数,分别表示游戏1到n不能在规定期限前完成的扣款数。
Output format
仅1行。表示小伟能赢取最多的钱。
Problem analysis
不难想到尽量把每一个活动都放在结束前一秒完成,为其他活动争取更大的空间
这只是一个理想的情况,有些活动不得不放弃,所以要优先完成扣款多的游戏
既然如此,把扣款多的游戏从多到少都尽量放在结束前1秒,把玩游戏的时间标记,如果这个时间已经被占用,就往前移,直到无法放置,这个贪心法则绝对是正确的,因为每个游戏最多占用1秒,一旦会导致后面某些游戏玩不了,那么即使不玩这个游戏,后面的游戏也最多只能玩1个,且扣更多的款
得证贪心法则:按游戏扣款从多到少按时间从后往前完成
Source code

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5+100; 4 typedef struct Node{ 5 int d,w; 6 bool operator < ( const Node &rhs ) const { 7 return w > rhs.w ; 8 } 9 }Node; 10 int n,m; 11 Node a[N]; 12 int vis[N]; 13 int main() 14 { 15 scanf("%d%d",&m,&n) ; 16 for ( int i = 0 ; i < n ; i++ ){ 17 scanf("%d",&a[i].d); 18 } 19 for ( int i = 0 ; i < n ; i++ ){ 20 scanf("%d",&a[i].w); 21 } 22 sort ( a , a+n ); 23 for ( int i = 0 ; i < n ; i++ ){ 24 int f = 0 ; 25 for(int j = a[i].d ; j >= 1 ; j -- ){ 26 if( vis[j] == 0 ){ 27 f = 1; 28 vis[j] = 1 ; 29 break; 30 } 31 } 32 if( !f ){ 33 //printf("(%d,%d)\n",a[i].d,a[i].w); 34 for ( int j = n ; j >= 1 ; j-- ){ 35 if( vis[j] == 0 ){ 36 vis[j] = 1 ; 37 break; 38 } 39 } 40 m = m - a[i].w ; 41 } 42 } 43 printf("%d\n",m); 44 return 0; 45 }
【习题1】数列极差
题目描述
“唉,你太自以为是了,这样吧,你先把这道数列极差问题发给他们,如果他们能有所领悟,那我会找时间和他们讨论一下时间旅行的可行性的。”李旭琳边说边在黑板上写了N个正整数组成的一个数列,并进行如下操作:每次擦去其中的两个数a和b,然后在数列中加入一个数a×b+1,如此下去直至黑板上剩下一个数,在所有按这种操作方式最后得到的数中,最大的为max,最小的为min,则该数列的极差定义为M=max-min。
现在请你编程,对于给定的数列,计算极差。
输入
输出
样例输入
3
1 2 3
0
样例输出
2
提示
每次合并最小的两个最后得到的是最大的,而每次合并最大的两个的时候最后得到的是最小的。
例如有6个数,即8 6 5 9 7 1
则min求值过程为: max求值过程为:
73 7 6 5 1 6 6 7 8 9
512 6 5 1 37 7 8 9
3073 5 1 37 57 9
15366 1 334 57
15367 19039
ans=19039-15367=3672
【贪心策略】:
从提示中可以看出来,这个题目就是考察在数列中找两个最大或者最小的进行相乘得到的值放回去,依次类推,知道数列中只剩下一个数为止。
【题解】:
我们用到的是优先队列来处理这个问题。只要每次从优先队列里拿出头两个进行相乘,然后返回去,直到队列中剩下一个数为止。
【代码】:

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5+10; 4 typedef long long ll; 5 ll a[N] ; 6 int n ; 7 int cmp ( ll a, ll b ){ 8 return a > b ; 9 } 10 int main() 11 { 12 int n; 13 while(~scanf("%d",&n),n){ 14 priority_queue < ll > Q1 ; 15 priority_queue < ll , vector<ll> , greater<ll> > Q2; 16 for(int i=0;i<n;i++){ 17 scanf("%lld",&a[i]); 18 Q1.push(a[i]); 19 Q2.push(a[i]); 20 } 21 22 ll Minz = Q1.top(); 23 //Q1.pop(); 24 while( !Q1.empty() ){ 25 ll t = Q1.top(); 26 Q1.pop(); 27 Minz = t ; 28 if( Q1.empty() ) break; 29 ll t2 = Q1.top(); 30 Q1.pop(); 31 ll tmp = t * t2 + 1; 32 Q1.push(tmp); 33 } 34 35 ll Maxz = Q2.top(); 36 //Q2.pop(); 37 while(!Q2.empty()){ 38 ll t = Q2.top(); 39 Q2.pop(); 40 Maxz = t ; 41 if( Q2.empty() ) break; 42 ll t2 = Q2.top(); 43 Q2.pop(); 44 ll tmp = t * t2 + 1; 45 Q2.push(tmp); 46 } 47 48 ll ans = Maxz - Minz ; 49 printf("%lld\n",ans); 50 } 51 52 return 0; 53 }
【习题2】数列分段
题目描述
输入
第二行包含N个空格隔开的非负整数Ai。
输出
样例输入
5 6
4 2 4 5 1
样例输出
3
提示
对于20%的数据,有N≤10;
对于40%的数据,有N≤1000;
对于100%的数据,有N≤105,M≤109,M大于所有数的最大值,Ai之和不超过109。
【题解】:
这个贪心策略非常明显,因为只要加到一定数就可以清空另起一段了,但是这个时候需要有一个小细节处理,因为这一段之和可能是 (等于m )或者是 (大于m),
这两种情况对于新增的一段是不一样的,所以要分开讨论。
1、等于m时,下一段 为 0 ,ans++
2、大于m是,下一段 为 当前值,ans++
最后如果结束时也要多加一个判断,因为可能最后一次不为0,所以要新增一段。具体可以看代码;
【代码】:

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e6+100; 4 int a[N]; 5 int main() 6 { 7 int n,m,cur=0,ans = 0 ; 8 cin >> n >> m ; 9 for(int i=1;i<=n;i++) cin >> a[i] ; 10 for(int i=1;i<=n;i++){ 11 cur = cur + a[i] ; 12 if( cur > m ){ 13 cur = a[i] ; 14 ans ++ ; 15 //cout << "### "<< cur << endl; 16 }else if( cur == m ){ 17 cur = 0 ; 18 //cout << "@@@ "<< cur << endl; 19 ans ++ ; 20 } 21 } 22 if( cur != 0 ){ 23 ans ++ ; 24 } 25 cout << ans << endl ; 26 return 0; 27 }
【习题3】线段
题目描述
输入
在接下来的n行中,每行有2个数ai,bi,描述每条线段。
输出
样例输入
3
0 2
2 4
1 3
样例输出
2
提示
对于20%的数据,n≤10;
对于50%的数据,n≤103;
对于70%的数据,n≤105;
对于100%的数据,n≤106,0≤ai<bi≤106。
【题解】:
其实这个题目就是例题1,活动安排,一模一样,直接复制粘贴交上就过了。
【贪心策略】:
建立结构体,然后按照右端点排序,先结束的活动排在前面,这样局部最优就可以扩展全局最优
【代码】:

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e6+10; 4 typedef struct Node 5 { 6 int S,F; 7 } Node ; 8 Node a[N]; 9 bool cmp ( Node u, Node v ) 10 { 11 if( u.F == v.F ) 12 { 13 return u.S < v.S ; 14 } 15 return u.F < v.F ; 16 } 17 int n ; 18 int main () 19 { 20 scanf("%d",&n); 21 for ( int i = 0 ; i < n ; i++ ) 22 { 23 scanf("%d%d",&a[i].S, &a[i].F ); 24 } 25 sort ( a, a+n,cmp ); 26 int ans = 1 ; 27 int F = a[0].F; 28 for ( int i = 1 ; i < n ; i++ ) 29 { 30 if( F <= a[i].S ) 31 { 32 F = a[i].F ; 33 ans ++ ; 34 } 35 } 36 printf("%d\n",ans ); 37 return 0; 38 }
【习题4】家庭作业
题目描述
每个作业的完成时间都是只有一天。例如,假设有7次作业的学分和完成时间如下:

老师在开学第一天就把所有作业都布置了,每个作业如果在规定的时间内交上来的话才有学分。每个作业的截止日期和学分可能是不同的。例如如果一个作业学分为10,要求在6天内交,那么要想拿到这10学分,就必须在第6天结束前交。
每个作业的完成时间都是只有一天。例如,假设有7次作业的学分和完成时间如下:
输入
接下来N行,每行包括两个整数,第一个整数表示作业的完成期限,第二个数表示该作业的学分。
输出
样例输入
7
1 6
1 7
3 2
3 1
2 4
2 5
6 1
样例输出
15
提示
对于20%的数据,N≤103;
对于40%的数据,N≤104;
对于60%的数据,N≤105;
对于100%的数据,N≤1e6,作业的完成期限均小于7×1e5
【分析】:
这个题目属于“带限期和罚款的单位时间任务调度”类型的题目;
然后这个题目不能像例题一样直接扫,因为例题的n仅仅是500,哪怕是O(n^3)都可以接受。
但是这个题目n=1e6,所以只能接受O(nlogn)的算法。
【题解】:
其实我想到的是二分来找位置删除的想法,但奈何没有删除最大小于的位置和set配合使用。
无奈只能上网看看被人的做法,后来看到了龙哥的做法,真的你龙哥还是你龙哥。
龙哥用并查集的想法模拟链表删除的写法,真令人感叹这是何等厉害的想法。
【步骤】:
首先需要两样东西,一个数组pre[N],模拟链表链接上一个的下标。
另一个是Find函数,就是 return pre[x] = Find( pre[x] )递归实现。
【具体代码】:

1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1e6+100; 5 typedef struct Node { 6 int w,d; 7 bool operator < ( const Node & rhs )const { 8 return w > rhs.w ; 9 } 10 }Node; 11 Node a[N]; 12 int pre[N]; 13 bool vis[N]; 14 int Find ( int x ){ 15 if ( vis[x] == 0 ) 16 return x; 17 return pre[x] = Find(pre[x]); 18 } 19 int main() 20 { 21 int n; 22 scanf("%d",&n); 23 for(int i=0,d,w;i<n;i++){ 24 scanf("%d%d",&d,&w); 25 a[i] = Node { w , d }; 26 pre[i] = i - 1 ; 27 } 28 sort ( a , a+n ); 29 int ans = 0 ; 30 for(int i=0 ; i<n ; i++ ){ 31 int f = Find(a[i].d); 32 if( f == 0 ) continue; 33 ans += a[i].w ; 34 vis[f] = 1 ; 35 } 36 printf("%d\n",ans); 37 }
【习题5】钓鱼
题目描述
输入
第二行一个整数H(1≤H≤20),表示佳佳的空闲时间
第三行有n个整数,依次表示每个湖第一个5分钟能钓到鱼的数量
第四行有n个整数,依次表示以后的每5分钟钓鱼数量比前一个5分钟钓鱼数量减少的数量
第五行有n−1个整数,Ti表示由第i个湖到第i+1个湖需要花5×Ti分钟的路程
输出
样例输入
3
1
4 5 6
1 2 1
1 2
样例输出
35
提示
在第1个湖钓15分钟,共钓得4+3+2=9条鱼;
在第2个湖钓10分钟,共钓得5+3=8条鱼;
在第3个湖钓20分钟,共钓得6+5+4+3=18条鱼;
从第1个湖到第2个湖,从第2个湖到第3个湖,共用时间15分钟,共得35条鱼,并且这是最多的数量。
【分析】:
这个题目是习题里面比较难的题目了,网上的题解有些是动态规划,然而我看了看其实用优先队列还是来得比较好用而且更好理解,其实一开始我也觉得这个题目很难全部分析出来,但是看了一些别人的代码分析一下,其实真的很好理解。
【题解】:
第一步:首先所有变量进行标准化,所有的时间全部转化为5分钟来计算,譬如一个小时有12个5分钟 ,钓鱼一次使用的第一个五分钟直接减1就行了。
第二步:a、以第i个池塘来讨论,
b、到第i个池塘必须要先扣除路程中要花费的5分钟,然后再进行钓鱼,用优先队列存放每一个池塘当前钓鱼的个数,
如果选择了第 j 个池塘钓鱼后,要更新第 j 个池塘的 钓鱼的数量,压会优先队列中。
第三步:不断执行第二步(b),直到时间为 0 ,或者优先队列中的所有的钓鱼量<=0 时则跳出,然后更新答案,返回第二步(a)
【代码】:

1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 110; 4 int dis[N],n,h; 5 typedef struct Node { 6 int Fish , Dec ; 7 bool operator < ( const Node & rhs ) const { 8 return Fish < rhs.Fish ; 9 } 10 }Node ; 11 12 Node a[N]; 13 int main() 14 { 15 scanf("%d%d",&n,&h); 16 for (int i=1;i<=n;i++) scanf("%d",&a[i].Fish); 17 for (int i=1;i<=n;i++) scanf("%d",&a[i].Dec); 18 for (int i=2;i<=n;i++) scanf("%d",&dis[i]); 19 20 h*=12 ; 21 22 int sum = 0 , ans = 0 ; 23 for ( int i = 1 ; i <= n ; i++ ){ 24 h -= dis[i] ; 25 sum = 0 ; 26 priority_queue < Node > Q ; 27 for ( int j = 1 ; j <= i ; j++ ){ 28 Q.push ( a[j] ) ; 29 } 30 int time = h ; 31 while ( time > 0 ){ 32 Node cur = Q.top() ; 33 Q.pop() ; 34 35 if( cur.Fish <= 0 ) 36 break ; 37 38 sum += cur.Fish ; 39 time -- ; 40 41 cur.Fish -= cur.Dec; 42 Q.push(cur); 43 } 44 ans = max ( ans , sum ) ; 45 } 46 printf("%d\n",ans); 47 return 0; 48 }
【习题6】糖果传递
题目描述
输入
接下来n行,每行一个整数ai,表示第i个小朋友得到的糖果的颗数.
输出
样例输入
4
1
2
5
4
样例输出
4
【题解】:
这个题目之后我会重新推导一遍,其实这个题目已经不属于贪心范围了,虽然说最后的做法是贪心的想法。
请大家移步到:洛谷题解区
里面的神仙分析非常透彻了已经,我就偷懒一下不写证明过程了。
结论为:排序后找到中位数,然后所有与中位数的绝对值之和就是答案。
【代码】:

1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N = 1e6+100; 5 ll a[N],Sum,sum[N]; 6 int main() 7 { 8 int n; 9 scanf("%d",&n); 10 for(int i=1;i<=n;i++){ 11 scanf("%lld",&a[i]); 12 Sum = Sum + a[i]; 13 } 14 for(int i=1;i<=n;i++){ 15 sum[i] = sum[i-1] + a[i] - Sum/n ; 16 } 17 sort ( sum + 1 , sum + 1 + n ); 18 int m = (n+1) / 2 ; 19 ll ans = 0 ; 20 for (int i=1;i<=n;i++){ 21 ans += abs( sum[i] - sum[m] ) ; 22 } 23 printf("%lld\n",ans ); 24 return 0 ; 25 }