三類基於貪心思想的區間覆蓋問題


一、區間完全覆蓋問題

問題描述:給定一個長度為m的區間,再給出n條線段的起點和終點(注意這里是閉區間),求最少使用多少條線段可以將整個區間完全覆蓋。

樣例:一個長度為8的區間,可選的線段有[2,6],[1,4],[3,6],[3,7],[6,8],[2,4],[3,5]。

求解過程:

1、將每一條線段按左端點遞增順序排列,如果左端點相同,按右端點遞增順序排列,排完序后為[1,4],[2,4],[2,6],[3,5],[3,6],[3,7],[6,8];

2、設置一個變量表示已覆蓋到的區間右端點,在剩下的線段中找出所有左端點小於等於當前已覆蓋到的區間右端點的線段,選擇右端點最大並且大於當前已覆蓋到的區間右端點,重復以上操作直至覆蓋整個區間;

3、模擬過程:假設第一次加入[1,4],那么下一次能夠選擇的線段有[2,6],[3,5],[3,6],[3,7],由於3小於4且7最大,所以下一次選擇[3,7]進行覆蓋,最后一次只能選擇[6,8],這個時候剛好覆蓋長為8的區間-->break;即所選3條線段就能覆蓋長度為8的大區間;

4、貪心證明:

要求用最少的線段進行覆蓋,那么選取的線段必然要盡量長,而已覆蓋到的區域之前的地方已經不用考慮了,可以理解成所有可覆蓋的左端點都已被覆蓋了,那么能夠使得線段更長的取決於右端點,左端點沒有太大的意義,所以選擇右端點來覆蓋。

題解報告:NYOJ #12 噴水裝置(二)

描述

有一塊草坪,橫向長w,縱向長為h,在它的橫向中心線上不同位置處裝有n(n<=10000)個點狀的噴水裝置,每個噴水裝置i噴水的效果是讓以它為中心半徑為Ri的圓都被潤濕。請在給出的噴水裝置中選擇盡量少的噴水裝置,把整個草坪全部潤濕。

輸入

第一行輸入一個正整數N表示共有n次測試數據。每一組測試數據的第一行有三個整數n,w,h,n表示共有n個噴水裝置,w表示草坪的橫向長度,h表示草坪的縱向長度。隨后的n行,都有兩個整數xi和ri,xi表示第i個噴水裝置的的橫坐標(最左邊為0),ri表示該噴水裝置能覆蓋的圓的半徑。

輸出

每組測試數據輸出一個正整數,表示共需要多少個噴水裝置,每個輸出單獨占一行。如果不存在一種能夠把整個草坪濕潤的方案,請輸出0。

樣例輸入

2
2 8 6
1 1
4 5
2 10 6
4 5
6 5

樣例輸出

1
2
解題思路:典型的區間完全覆蓋問題。由於噴水裝置是安置在橫向中心線上並且圓具有對稱性,故只需取高度的一半,然后將每個噴水裝置能夠覆蓋的區間范圍映射成在x軸的長度,然后按上面的方法貪心選線段即可。
AC代碼:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=10005;
 4 int t,n,k,cnt,pos,beg;double w,h,lb,xi,ri,maxv;pair<double,double> itv[maxn];bool flag;
 5 int main(){
 6     while(cin>>t){
 7         while(t--){
 8             cin>>n>>w>>h;h/=2.0;pos=cnt=0;//h取一半  9             for(int i=0;i<n;++i){
10                 cin>>xi>>ri;
11                 if(ri<h)continue;//ri==h也要算
12                 itv[pos].first=xi-sqrt(ri*ri-h*h);
13                 itv[pos++].second=xi+sqrt(ri*ri-h*h);
14             }
15             sort(itv,itv+pos);lb=0;beg=0;flag=false;
16             if(itv[0].first>0){cout<<0<<endl;continue;}//按左端點排序只需查看最左邊的端點是否滿足條件即可,最右邊的端點在下面有判斷
17             while(lb<w){
18                 maxv=0;
19                 for(k=beg;k<pos&&itv[k].first<=lb;++k)//itv[k].first<=lb這樣保證整個區間是連續的,即草坪都會被潤濕
20                     maxv=max(maxv,itv[k].second);//找線段左端點在lb以內右端點能覆蓋到的最遠距離
21                 if(maxv>lb)cnt++,lb=maxv,beg=k;//如果有一條線段右端點比當前已覆蓋的區間右端點lb還大,那么就更新已覆蓋的右端點值,同時計數器加1
22                 else {flag=true;break;}//否則說明不能覆蓋整個區間,直接退出,輸出0
23             }
24             if(flag)cout<<0<<endl;
25             else cout<<cnt<<endl;
26         }
27     }
28     return 0;
29 }

題解報告:NYOJ #6 噴水裝置(一)

描述

現有一塊草坪,長為20米,寬為2米,要在橫中心線上放置半徑為Ri的噴水裝置,每個噴水裝置的效果都會讓以它為中心的半徑為實數Ri(0<Ri<15)的圓被濕潤,這有充足的噴水裝置i(1<i<600)個,並且一定能把草坪全部濕潤,你要做的是:選擇盡量少的噴水裝置,把整個草坪的全部濕潤。

輸入

第一行m表示有m組測試數據;
每一組測試數據的第一行有一個整數數n,n表示共有n個噴水裝置,隨后的一行,有n個實數ri,ri表示該噴水裝置能覆蓋的圓的半徑。

輸出

輸出所用裝置的個數

樣例輸入

2
5
2 3.2 4 4.5 6 
10
1 2 3 1 2 1.2 3 1.1 1 2

樣例輸出

2
5
解題思路:將每個能噴灑到草坪邊緣的噴水裝置的噴灑范圍映射成在x軸的長度,然后按線段長度遞增順序排列,再從后往前貪心選線段即可得到選擇最少的噴水裝置來潤濕整個草坪。
AC代碼:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int t,n,cnt;double ans,ri,dt[605];
 4 int main(){
 5     while(cin>>t){
 6         while(t--){
 7             cin>>n;ans=0;cnt=0;
 8             for(int i=0;i<n;++i)cin>>ri,dt[i]=ri>1?sqrt(ri*ri-1):0;//這里可以設置為0,因為題目已經保證一定可以將草坪全部潤濕
 9             sort(dt,dt+n);
10             for(int i=n-1;ans<=10.0&&i>=0;--i)cnt++,ans+=dt[i];//從后往前能選出最少數量的噴水裝置,且一定能將草坪潤濕
11             cout<<cnt<<endl;
12         }
13     }
14     return 0;
15 }

 二、最大不相交區間數問題

問題描述:數軸上有n個區間$ [a_i,b_i] $,要求選擇盡量多個區間,使得這些區間兩兩沒有公共點。

樣例:數軸上有7個區間,可選的區間有[2,6],[1,4],[3,6],[3,7],[6,8],[2,4],[3,5]。

求解過程:

1、按區間右端點遞增順序排列,如果右端點相同,按左端點遞增順序排序,排完序后為[1,4],[2,4],[3,5],[2,6],[3,6],[3,7],[6,8];
2、第一次選擇[1,4],接下來只能選擇[6,8],即當前數軸上最多只能選擇兩個不相交的區間。

3、貪心證明:為了選擇更多的區間個數,先按區間右端點遞增順序排列,然后順序處理每個區間,如果它與當前已選的所有區間都沒有相交,則選擇該區間,否則不選。接下來證明區間左端點a1,a2…對右端點沒有影響:

①當a1>a2時,區間2包含區間1,顯然不能選擇區間2,因為選擇區間1會留下更多的區域。不僅區間2如此,以后所有區間中只要有一個i滿足a1>ai,i都不要選,所以此種情況下,選擇區間1是明智的,與策略一致。

②排除情況1后,一定有a1<=a2<=a3……,此時選擇區間1是最優策略,說明無論左端點是大是小,只要對區間右端點進行排序,然后貪心選擇不相交的區間就可得到數軸上最多不相交的區間個數,即這個策略是正確的。

 

題解報告:NYOJ #14 會場安排問題

描述

學校的小禮堂每天都會有許多活動,有時間這些活動的計划時間會發生沖突,需要選擇出一些活動進行舉辦。小劉的工作就是安排學校小禮堂的活動,每個時間最多安排一個活動。現在小劉有一些活動計划的時間表,他想盡可能的安排更多的活動,請問他該如何安排。

輸入

第一行是一個整型數m(m<100)表示共有m組測試數據。
每組測試數據的第一行是一個整數n(1<n<10000)表示該測試數據共有n個活動。
隨后的n行,每行有兩個正整數Bi,Ei(0<=Bi,Ei<10000),分別表示第i個活動的起始與結束時間(Bi<=Ei)

輸出

對於每一組輸入,輸出最多能夠安排的活動數量。
每組的輸出占一行

樣例輸入

2
2
1 10
10 11
3
1 10
10 11
11 20

樣例輸出

1
2
解題思路:按結束時間早進行排序,然后貪心選擇不相交區間即可。
AC代碼:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long LL;
 4 const int maxn=10005;
 5 int t,n,tmp,ans;pair<int,int> itv[maxn];
 6 int main(){
 7     while(cin>>t){
 8         while(t--){
 9             cin>>n;
10             for(int i=0;i<n;++i)cin>>itv[i].second>>itv[i].first;
11             sort(itv,itv+n);tmp=-1;ans=0;//按結束時間早進行排序
12             for(int i=0;i<n;++i)
13                 if(tmp<itv[i].second)ans++,tmp=itv[i].first;
14             cout<<ans<<endl;
15         }
16     }
17     return 0;
18 }

三、區間選點問題

問題描述:數軸上有n個閉區間 $[a_i,b_i] $,要求選取盡量少的點,使得每個區間內都至少有一個點(不同區間內含的點可以是同一個)。

樣例略。

求解過程:

1、按左端點遞增順序排序,如果左端點相同,按右端點遞增順序排序,個人覺得這種比較好理解,當然也可以按右端點遞增順序排序。

2、①從第一個區間右端點開始貪心往后找,如果下一個區間的左端點大於當前已選區間的右端點,說明要新開一個點,計數器加1,同時更新右區間能覆蓋的最遠距離;②如果下一個區間右端點小於當前已選區間的右端點,說明共享的線段范圍縮短了,那么就更新區間右端點為下一個區間右端點,重復以上操作,直至篩選完所有區間。

貪心證明:為了選擇最少的點使得每個區間內至少含有一個點,考慮按區間左端點遞增順序排序,如果左端點相同,則按區間右端點遞增順序排序,然后以第一個區間右端點作為第一個點能覆蓋的最大范圍。①當b1>b2時,顯然此時一個點能覆蓋最大的區域右邊界變為b2,同理,以后只要滿足 $ b_1 > b_i $,一個點能覆蓋的區域右邊界就會變為 $ b_i $,顯然這是正確的;②當b1<a2時,顯然一個點不能覆蓋到區間2上,所以需新開一個點,此時能覆蓋的區域最右邊界變為b2,同理,以后只要滿足 $ b1 < a_i $,則都要新開一個點,並且其能覆蓋的區域右邊界將變為 $ b_i $,顯然這也是正確的;③ 當b1<b2時,顯然區間1和區間2有公共的部分,但此時一個點能覆蓋的區域最右邊界還是為 b1,無需更新區域最右邊界,同理,對於以后只要滿足 $ b_1<b_i $,都無需新開一個點,也無需更新能覆蓋區域的最右邊界,顯然這也是正確的。綜上,按區間左端點遞增的順序排序,再按規則貪心選點的策略是正確的。

題解報告:NYOJ #891 找點

描述

上數學課時,老師給了LYH一些閉區間,讓他取盡量少的點,使得每個閉區間內至少有一個點。但是這幾天LYH太忙了,你們幫幫他嗎?

輸入

多組測試數據。
每組數據先輸入一個N,表示有N個閉區間(N≤100)。
接下來N行,每行輸入兩個數a,b(0≤a≤b≤100),表示區間的兩個端點。

輸出

輸出一個整數,表示最少需要找幾個點。

樣例輸入

4
1 5
2 4
1 4
2 3
3
1 2
3 4
5 6
1
2 2

樣例輸出

1
3
1
解題思路:按左端點遞增順序排序,然后按上面的求解方法貪心選點即可。
AC代碼:
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long LL;
 4 const int maxn=105;
 5 int n,tmp,ans;pair<int,int> itv[maxn];
 6 int main(){
 7     while(cin>>n){
 8         for(int i=0;i<n;++i)cin>>itv[i].first>>itv[i].second;
 9         sort(itv,itv+n);tmp=itv[0].second;ans=1;
10         for(int i=0;i<n;++i){
11             if(tmp<itv[i].first)ans++,tmp=itv[i].second;
12             else if(tmp>itv[i].second)tmp=itv[i].second;
13         }
14         cout<<ans<<endl;
15     }
16     return 0;
17 }

題解報告:poj 1328 Radar Installation

Description

Assume the coasting is an infinite straight line. Land is in one side of coasting, sea in the other. Each small island is a point locating in the sea side. And any radar installation, locating on the coasting, can only cover d distance, so an island in the sea can be covered by a radius installation, if the distance between them is at most d. 
We use Cartesian coordinate system, defining the coasting is the x-axis. The sea side is above x-axis, and the land side below. Given the position of each island in the sea, and given the distance of the coverage of the radar installation, your task is to write a program to find the minimal number of radar installations to cover all the islands. Note that the position of an island is represented by its x-y coordinates. 
 
Figure A Sample Input of Radar Installations

Input

The input consists of several test cases. The first line of each case contains two integers n (1<=n<=1000) and d, where n is the number of islands in the sea and d is the distance of coverage of the radar installation. This is followed by n lines each containing two integers representing the coordinate of the position of each island. Then a blank line follows to separate the cases. 
The input is terminated by a line containing pair of zeros 

Output

For each test case output one line consisting of the test case number followed by the minimal number of radar installations needed. "-1" installation means no solution for that case.

Sample Input

3 2
1 2
-3 1
2 1

1 2
0 2

0 0

Sample Output

Case 1: 2
Case 2: 1
解題思路:典型的區間選點,將雷達能覆蓋的范圍映射為x軸上的線段長度,然后貪心區間選點即可。
AC代碼:
 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cmath>
 4 using namespace std;
 5 const int maxn=1005;
 6 int n,d,ans,pos,cnt=1,x,y;double tmp;
 7 struct node{double l,r;}point[maxn];
 8 bool cmp(node a,node b){return a.l<b.l;}
 9 int main(){
10     while(cin>>n>>d&&(n|d)){//注意這里:n|d,表示n和d同時為0時,程序才退出
11         ans=1;pos=0;
12         for(int i=0;i<n;++i){
13             cin>>x>>y;
14             if(y>d){ans=-1;continue;}//根號下只能為非負數
15             point[pos].l=1.0*x-sqrt(1.0*d*d-y*y);//以每個島嶼為圓心,半徑為d畫圓,其與x軸最后只有兩個交點
16             point[pos++].r=1.0*x+sqrt(1.0*d*d-y*y);
17         }
18         sort(point,point+pos,cmp);tmp=point[0].r;
19         for(int i=1;i<pos&&ans!=-1;++i){
20             if(tmp<point[i].l){ans++;tmp=point[i].r;}//如果已選線段與當前線段不相交,那么就設置一個新的雷達,然后更新tmp為其右端點值
21             else if(tmp>point[i].r)tmp=point[i].r;//可以覆蓋掉下一條線段,但此時區間右端點縮短為下一條線段的右端點,說明覆蓋的范圍縮短了
22         }
23         cout<<"Case "<<cnt++<<": "<<ans<<endl;
24     }
25     return 0;
26 }

 


免責聲明!

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



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