一、區間完全覆蓋問題
題目
給定一個長度為m的區間,再給出n條線段的起點和終點(注意這里是閉區間),求最少使用多少條線段可以將整個區間完全覆蓋。
解析
先將所有線段按起點從小到大排序。排完序后,枚舉每一個線段(被其它線段包含的線段不用考慮,因為很明顯包含它的線段比它更優),將其作為最左端的線段,
再在剩下的左端點小於等於最左端的線段的右端點的線段中(若沒有則無解),找到右端點最大的一個線段,即貪心,很顯然這是最優的,因為其左端都被最左端的線段覆蓋了,
也就沒有覆蓋到任何地方,則其右端點越大,其右端覆蓋到的地方也就最優。
反復重復上一步,直到覆蓋完整個長度為m的區間,就能得到最少的線段數。
Code

#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> using namespace std; struct rec{ int l,r; }a[1001]; bool cmp(rec x,rec y) { return x.l<y.l; //按左端點從小到大排 } int m,n,ll,minn=0x7f7f7f7f; void q(int x,int ans) { if(a[x].r-ll>=m-1) //覆蓋總長度達到 { minn=min(minn,ans); return ; } int temp=0; for(int i=x+1;i<=n;i++) { if(a[i].l<=a[x].r) //找左端點小於當前線段右端點的 { if(a[i].r>a[temp].r) temp=i; //找右端點最大區間 } else break; //順序排序,如果左端點大於當前線段右端點,后面肯定也大於 if(temp!=0) q(temp,ans+1); } } int main() { a[0].r=-1; //特殊處理 cin>>m>>n; for(int i=1;i<=n;i++) cin>>a[i].l>>a[i].r; sort(a+1,a+n+1,cmp); //順序排序 for(int i=1;i<=n;i++) { ll=a[i].l; //記錄起點 q(i,1); } cout<<minn; return 0; }
二、最大不相交區間數問題
題目
數軸上有n個開區間[ai,bi],要求選擇盡量多個區間,使得這些區間兩兩沒有公共點。
解析
先對區間左端點進行從大到小排序,左端點相同按右端點從小到大排序,
再依次選出左端點最大的區間,當待選擇區間與已選區間集合相交時,舍棄待選區間,
每次直接從排好后的第一個開始選,是為了使左邊預留區間最大。
Code

#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> using namespace std; struct rec{ int a,b; }ra[1001]; bool cmp(rec x,rec y) { if(x.a!=y.a) return x.a>x.b;//左端點從大到小排序 else return y.a<y.b; //左端點相同,按右端點從小到大排序,即左端點相同,優先選擇短的 } int n,ans=1,lasta; int main() { cin>>n; for(int i=0;i<n;i++) cin>>ra[i].a>>ra[i].b; sort(ra,ra+n,cmp); lasta=ra[0].a;//選中a最大的第一個 for(int i=1;i<n;i++) { if(ra[i].b<=lasta) //不重疊 { lasta=ra[i].a; ans++; } } cout<<ans; return 0; }
三、區間選點問題
題目
數軸上有n個閉區間 [ai,bi],要求選取盡量少的點,使得每個區間內都至少有一個點(不同區間內含的點可以是同一個)。
解析
先按左端點從小到大排序每個區間,若左端點相同,則按右端點從小到大排序。
1.再從第一個區間貪心往后找,如果下一個區間左端點大於該區間的右端點,則需增加一個點,反之共用一個點;
2.若下個區間右端點小於當前區間右端點,說明共用的區間范圍變小了,則更新區間右端點為下一個區間右端點。
不斷重復1、2兩個步驟,直到每個區間都有點。
為什么呢?因為在排完序后,
①當b1>bi時,顯然此時一個點能覆蓋最大的區域右邊界變為bi;
②當b1<ai時,顯然一個點不能覆蓋到區間i上,所以需新開一個點,此時能覆蓋的區域最右邊界變為bi;
③ 當b1<bi時,顯然區間1和區間i有公共的部分,但此時一個點能覆蓋的區域最右邊界還是為b1,無需更新區域最右邊界。
綜上所述,排序后貪心選點是正確的。
Code

#include <algorithm> #include <iostream> #include <cstring> #include <string> #include <cstdio> #include <cmath> using namespace std; struct rec{ int a,b; }ra[1001]; bool cmp(rec x,rec y) { if(x.a!=y.a) return x.a<y.a; //按左端點從小到大排序 else return x.b<y.b; //左端點相同,則按右端點從小到大排序 } int n,ans=1,r; int main() { cin>>n; for(int i=1;i<=n;i++) cin>>ra[i].a>>ra[i].b; sort(ra+1,ra+n+1,cmp); //順序排序 r=ra[1].b; //記錄上一個區間右端點 for(int i=2;i<=n;i++) { if(ra[i].a>r) //左端點大於上個區間右端點 { ans++; //增加一個點 r=ra[i].b; } else if(ra[i].b<r) r=ra[i].b; //右端點小於上個區間右端點,更新上個區間右端點 } cout<<ans; return 0; }