貪心算法練習:區間覆蓋問題

備註:本文純粹是閱讀劉汝佳的《算法競賽入門經典(第二版)》的一個筆記。筆記文字基本是原文。這裏僅僅添加了源代碼文件ios

問題模型:數軸上有n個閉區間[ai, bi],選擇儘可能少的區間覆蓋一個指定區間 [ start,tail ]算法

 

【分析】ide

本題的突破口仍然是區間包含和排序掃描,不過先要進行一次預處理。 每一個區間在[start,tail]外的部分都應該預先被切掉,由於它們的存在是毫無心義的 預處理後,在相互包含的狀況下,小區間顯然不該該考慮函數

把各區間按照a從小到大排序。 若是區間1的起點不是s,無解(由於其餘區間的起點更大,不可能覆蓋到s點),不然選擇起點在s的最長區間選擇此區間[ai, bi] 後,新的起點應該設置爲bi,而且忽略全部區間在bi以前的部分,就像預處理同樣。 雖然貪心策略比上題複雜,可是仍然只須要一次掃描,以下圖所示。 s爲當前有效起點(此前部分已被覆蓋),則應該選擇區間2。測試

上面是原文做者劉汝佳的講解。有些地方感受講解的太過玄乎,不是很好理解,其實就是下面這個意思:優化

把各區間按照左端點a從小到大排序,當左端點相等,按右端點從大到小排序。若是區間1的起點不是s,無解,不然起點在s的最長區間。選擇[ai,bi]後,新的起點設置成bi。直至覆蓋整個線段spa

下面約定輸入數據格式:.net

第一行輸入三個整數n,start和tail,表示有n個區間能夠選擇,須要覆蓋的目標區間是[start,tail].3d

接着輸入n行,每行兩個整數表示一個閉區間。code

 

具體代碼以下:

主函數部分:

 1 #include <iostream>
 2 #include<stdio.h>
 3 #include<algorithm>
 4 using namespace std;  5 
 6 struct obj  7 {  int x,y,select; };  8 
 9 int n,start,tail,cnt=0,ans=0; 10 struct obj a[100]; 11 
12 //按區間左端點升序排序,左端點相等則按右端點降序排序。
13 int cmp(const struct obj &a,const struct obj &b) 14 { 15     if(a.x==b.x) return a.y>b.y; 16     else return a.x<b.x; 17 } 18 void solve1(); 19 void solve2(); 20 
21 int main() 22 { 23     int i,xx,yy; 24     freopen("data.in","r",stdin); 25 
26     scanf("%d%d%d",&n,&start,&tail); 27     for(i=0;i<n;i++) 28  { 29         scanf("%d%d",&xx,&yy); 30         if(xx>tail||yy<start) continue;//這三行是一個簡單的優化。不要也能夠。
31         if(xx<start) xx=start; 32         if(yy>tail) yy=tail; 33 
34         a[cnt].select=0; 35         a[cnt].x=xx; 36         a[cnt++].y=yy; 37  } 38 
39     sort(a,a+cnt,cmp); 40     /*for(i=0;i<cnt;i++) 41  printf("%2d %2d %2d\n",a[i].x,a[i].y,a[i].select); 42  printf("\n");*/
43 
44  solve2(); 45     return 0; 46 }

關鍵函數代碼:建議看solve2().

solve1( )函數:意思比較容易理解,更符合常規思惟

 1 void solve1()//思路沒有錯,只是效率較低
 2 {  3     int i,newStart=start,maxY,selectedID;  4     if(a[0].x>start)//若左端點最小的區間都沒法覆蓋出發點,那其餘區間更加沒法覆蓋出發點。此時無解。
 5  {  6         ans=-1;  7         printf("no answer!\n");  8         return ;  9  } 10     //掃描全部區間,選擇「不曾被選用,並且左端點包含出發點,右端點最大的區間」
11     while(newStart<tail) 12  { 13         maxY=newStart; 14         selectedID=-1; 15         for(i=0;i<cnt;i++) 16  { 17             if(a[i].select==0&&a[i].x<=newStart&&a[i].y>maxY)// 18  { 19                 maxY=a[i].y; 20                 selectedID=i; 21  } 22  } 23         if(selectedID==-1)//沒有找到合適的區間能包含出發點.(中間斷開了,不能連續覆蓋)
24  { 25             ans=-1; 26             printf("no answer!\n"); 27             return ; 28  } 29         else
30         {   //找到了合適的、最優的區間,因此選中的區間個數增長1
31             ans++; 32             a[selectedID].select=1; 33             newStart=a[selectedID].y; 34  } 35  } 36     printf("%d\n",ans); 37     for(i=0;i<cnt;i++) 38  { 39         printf("%2d %2d %2d\n",a[i].x,a[i].y,a[i].select); 40  } 41 }
View Code

solve2( )函數:效率較高。

 1 void solve2()  2 {  3     int i,newStart=start,maxY,selectedID;  4     if(a[0].x>start)//若左端點最小的區間都沒法覆蓋出發點,那其餘區間更加沒法覆蓋出發點。此時無解。
 5  {  6         ans=-1;  7         printf("no answer!\n");  8         return ;  9  } 10 
11     //掃描全部區間,選擇「不曾被選用,並且左端點包含出發點,右端點最大的區間」
12     i=0; 13     while(newStart<tail)//下面for的循環開始點不從0開始,避免重複搜索
14  { 15         maxY=newStart; 16         selectedID=-1; 17         for( ;a[i].x<=newStart&&i<cnt;i++)//注意a[i].x<=ss不能放在下面的if條件中
18  { 19             if(a[i].y>maxY) 20  { 21                 maxY=a[i].y; 22                 selectedID=i; 23  } 24  } 25         if(selectedID==-1)//沒有找到合適的區間能包含出發點.(中間斷開了,不能連續覆蓋)
26  { 27             ans=-1; 28             printf("no answer!\n"); 29             return ; 30  } 31         else
32         {   //找到了合適的、最優的區間,因此選中的區間個數增長1
33             ans++; 34             a[selectedID].select=1; 35             newStart=a[selectedID].y; 36  } 37  } 38     printf("%d\n",ans); 39     for(i=0;i<cnt;i++) 40  { 41         printf("%2d %2d %2d\n",a[i].x,a[i].y,a[i].select); 42  } 43 }
View Code

 

完整代碼以下:

 1 #include <iostream>
 2 #include<stdio.h>
 3 #include<algorithm>
 4 using namespace std;  5 
 6 struct obj  7 {  int x,y,select; };  8 
 9 int n,start,tail,cnt=0,ans=0;  10 struct obj a[100];  11 
 12 //按區間左端點升序排序,左端點相等則按右端點降序排序。
 13 int cmp(const struct obj &a,const struct obj &b)  14 {  15     if(a.x==b.x) return a.y>b.y;  16     else return a.x<b.x;  17 }  18 void solve1()//思路沒有錯,只是效率較低
 19 {  20     int i,newStart=start,maxY,selectedID;  21     if(a[0].x>start)//若左端點最小的區間都沒法覆蓋出發點,那其餘區間更加沒法覆蓋出發點。此時無解。
 22  {  23         ans=-1;  24         printf("no answer!\n");  25         return ;  26  }  27     //掃描全部區間,選擇「不曾被選用,並且左端點包含出發點,右端點最大的區間」
 28     while(newStart<tail)  29  {  30         maxY=newStart;  31         selectedID=-1;  32         for(i=0;i<cnt;i++)  33  {  34             if(a[i].select==0&&a[i].x<=newStart&&a[i].y>maxY)//  35  {  36                 maxY=a[i].y;  37                 selectedID=i;  38  }  39  }  40         if(selectedID==-1)//沒有找到合適的區間能包含出發點.(中間斷開了,不能連續覆蓋)
 41  {  42             ans=-1;  43             printf("no answer!\n");  44             return ;  45  }  46         else
 47         {   //找到了合適的、最優的區間,因此選中的區間個數增長1
 48             ans++;  49             a[selectedID].select=1;  50             newStart=a[selectedID].y;  51  }  52  }  53     printf("%d\n",ans);  54     for(i=0;i<cnt;i++)  55  {  56         printf("%2d %2d %2d\n",a[i].x,a[i].y,a[i].select);  57  }  58 }  59 void solve2()  60 {  61     int i,newStart=start,maxY,selectedID;  62     if(a[0].x>start)//若左端點最小的區間都沒法覆蓋出發點,那其餘區間更加沒法覆蓋出發點。此時無解。
 63  {  64         ans=-1;  65         printf("no answer!\n");  66         return ;  67  }  68 
 69     //掃描全部區間,選擇「不曾被選用,並且左端點包含出發點,右端點最大的區間」
 70     i=0;  71     while(newStart<tail)//下面for的循環開始點不從0開始,避免重複搜索
 72  {  73         maxY=newStart;  74         selectedID=-1;  75         for( ;a[i].x<=newStart&&i<cnt;i++)//注意a[i].x<=ss不能放在下面的if條件中
 76  {  77             if(a[i].y>maxY)  78  {  79                 maxY=a[i].y;  80                 selectedID=i;  81  }  82  }  83         if(selectedID==-1)//沒有找到合適的區間能包含出發點.(中間斷開了,不能連續覆蓋)
 84  {  85             ans=-1;  86             printf("no answer!\n");  87             return ;  88  }  89         else
 90         {   //找到了合適的、最優的區間,因此選中的區間個數增長1
 91             ans++;  92             a[selectedID].select=1;  93             newStart=a[selectedID].y;  94  }  95  }  96     printf("%d\n",ans);  97     for(i=0;i<cnt;i++)  98  {  99         printf("%2d %2d %2d\n",a[i].x,a[i].y,a[i].select); 100  } 101 } 102 int main() 103 { 104     int i,xx,yy; 105     freopen("data.in","r",stdin); 106 
107     scanf("%d%d%d",&n,&start,&tail); 108     for(i=0;i<n;i++) 109  { 110         scanf("%d%d",&xx,&yy); 111         if(xx>tail||yy<start) continue;//這三行是一個簡單的優化。不要也能夠。
112         if(xx<start) xx=start; 113         if(yy>tail) yy=tail; 114 
115         a[cnt].select=0; 116         a[cnt].x=xx; 117         a[cnt++].y=yy; 118  } 119 
120     sort(a,a+cnt,cmp); 121     /*for(i=0;i<cnt;i++) 122  printf("%2d %2d %2d\n",a[i].x,a[i].y,a[i].select); 123  printf("\n");*/
124 
125  solve2(); 126     return 0; 127 }
View Code

 

下面是一個測試數據樣例

輸入:

10 1 12
-5 -2
-10 -3
1 2
5 10
-3 5
3 20
8 12
11 16
-3 0
0 9









輸出:

2
1 9 1
1 5 0
1 2 0
3 12 1
5 10 0
8 12 0
11 12 0






 

拓展閱讀:

https://blog.csdn.net/xia842655187/article/details/51944829

相關文章
相關標籤/搜索