1. 程式人生 > >三類基於貪心思想的區間覆蓋問題

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

一、區間完全覆蓋問題

問題描述:給定一個長度為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 }