【信息學奧賽一本通 提高組】第一章 貪心算法


一、貪心算法的特點:

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個活動的集合E={1,2,..,n},其中每個活動都要求使用同一資源,如演講會場等,而在同一時間內只有一個活動能使用這一資源。每個活動i都有一個要求使用該資源的起始時間si和一個結束時間fi,且si<fi。如果選擇了活動i,則它在時間區間[si,fi)內占用資源。若區間[si,fi)與區間[sj,fj)不相交,則稱活動i與活動j是相容的。也就是說,當fi≤sj或fj≤si時,活動i與活動j相容。選擇出由互相兼容的活動組成的最大集合。

輸入

第一行一個整數n(1≤n≤1000);
接下來的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進行選擇;對於當前區間,若集合中的數不能覆蓋它,則將區間末尾的數加入集合。

 

【例題】 種樹

題目描述

   一條街的一邊有幾座房子。因為環保原因居民想要在路邊種些樹。路邊的地區被分割成塊,並被編號成1..N。每個部分為一個單位尺寸大小並最多可種一棵樹。每個居民想在門前種些樹並指定了三個號碼B,E,T。這三個數表示該居民想在B和E之間最少種T棵樹。當然,B≤E,居民必須記住在指定區不能種多於區域地塊數的樹,所以T≤E-B+l。居民們想種樹的各自區域可以交叉。你的任務是求出能滿足所有要求的最少的樹的數量。

  寫一個程序計算最少要種樹的數量。

輸入

第一行包含數據N,區域的個數(0<N≤30000);
第二行包含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】噴水裝置

題目描述

  長L米,寬W米的草坪里裝有n個澆灌噴頭。每個噴頭都裝在草坪中心線上(離兩邊各W/2米)。我們知道每個噴頭的位置(離草坪中心線左端的距離),以及它能覆蓋到的澆灌范圍。
  請問:如果要同時澆灌整塊草坪,最少需要打開多少個噴頭?
                                                

輸入

輸入包含若干組測試數據。
第一行一個整數T表示數據組數;
每組數據的第一行是整數n、L和W(n≤15000);
接下來的n行,每行包含兩個整數,給出一個噴頭的位置和澆灌半徑(上面的示意圖是樣例輸入第一組數據所描述的情況)。

輸出

對每組測試數據輸出一個數字,表示要澆灌整塊草坪所需噴頭數目的最小值。如果所有噴頭都打開也不能澆灌整塊草坪,則輸出 -1

樣例輸入

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】加工生產調度

題目描述

有n個部件需在A、B機器上加工,每個工件都必須經過先A后B兩道工序。
已知:部件i在A、B機器上的加工時間分別為ai,bi。
問:如何安排n個工件的加工順序,才能使得總加工時間最短?

 

輸入

第1行僅一個整數n (0<n<1000),表示產品的數量;
第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。

  現在請你編程,對於給定的數列,計算極差。

輸入

輸入包含多個測試集。每個測試集的第一個數N表示 正整數序列長度(0≤N≤50000),隨后是N個正整數。N為0表示輸入結束。

輸出

每個結果一行。

 

樣例輸入

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,現要將其分成連續的若干段,並且每段和不超過M(可以等於M),問最少能將其分成多少段使得滿足要求。

輸入

第一行包含兩個正整數N,M,表示了數列Ai的長度與每段和的最大值;
第二行包含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條線段,選取其中k條線段使得這k條線段兩兩沒有重合部分,問k最大為多少。

輸入

  第一行為一個正整數n;
  在接下來的n行中,每行有2個數ai,bi,描述每條線段。

輸出

  輸出一個整數,為k的最大值。

 

樣例輸入

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】家庭作業

題目描述

老師在開學第一天就把所有作業都布置了,每個作業如果在規定的時間內交上來的話才有學分。每個作業的截止日期和學分可能是不同的。例如如果一個作業學分為10,要求在6天內交,那么要想拿到這10學分,就必須在第6天結束前交。
每個作業的完成時間都是只有一天。例如,假設有7次作業的學分和完成時間如下:

         

老師在開學第一天就把所有作業都布置了,每個作業如果在規定的時間內交上來的話才有學分。每個作業的截止日期和學分可能是不同的。例如如果一個作業學分為10,要求在6天內交,那么要想拿到這10學分,就必須在第6天結束前交。
每個作業的完成時間都是只有一天。例如,假設有7次作業的學分和完成時間如下:

 

輸入

第一行一個整數N,表示作業的數量;
接下來N行,每行包括兩個整數,第一個整數表示作業的完成期限,第二個數表示該作業的學分。

 

輸出

輸出一個整數表示可以獲得的最大學分。保證答案不超過C/C++的int范圍。

 

樣例輸入

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】釣魚

題目描述

  在一條水平路邊,有n個釣魚湖,從左到右編號為1,2,…,n。佳佳有H個小時的空余時間,他希望利用這個時間釣到更多的魚。他從1出發,向右走,有選擇的在一些湖邊停留一定的時間(是5分鍾的倍數)釣魚。最后在某一個湖邊結束釣魚。佳佳從第i個湖到第i+1個湖需要走5×Ti分鍾路,還測出在第i個湖停留,第一個5分鍾可以釣到Fi條魚,以后每再釣5分鍾,可以釣到的魚量減少Di,若減少后的魚量小於0,則減少后的魚量為0。為了簡化問題,佳佳假定沒有其他人釣魚,也沒有其他因素影響他釣到期望數量的魚。請編程求出佳佳最多能釣魚的數量。

 

輸入

  第一行一個整數n(2≤n≤100),表示湖的個數
  第二行一個整數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個糖果。每人只能給左右兩人傳遞糖果。每人每次傳遞一個糖果代價為1。

輸入

  第一行一個正整數nn<=1'000'000,表示小朋友的個數.
  接下來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 }
糖果傳遞

 


免責聲明!

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



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