三類貪心區間覆蓋問題


一、區間完全覆蓋問題

題目

給定一個長度為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;
}
View Code

 

 

 

 

 

 

 

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

題目

數軸上有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;
}
View Code

 

 

 

 

 

 

 

三、區間選點問題

題目

數軸上有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;
}
View Code

 


免責聲明!

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



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