備註:本文純粹是閱讀劉汝佳的《算法競賽入門經典(第二版)》的一個筆記。筆記文字基本是原文。這裏僅僅添加了源代碼文件。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 }
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 }
完整代碼以下:


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 }
下面是一個測試數據樣例
輸入:
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
拓展閱讀: