区间相关问题总结

来源:互联网 发布:vb boolean 默认值 编辑:程序博客网 时间:2024/05/17 05:52

一、选择不相交区间

数轴上有n个区间[ai,bi],要求选择尽量多个区间,使得这些区间两两没有公共点

贪心策略:

按照b1<=b2<=b3…的方式排序,然后从前向后遍历,每当遇到可以加入集合的区间,就把它加入集合。(集合代表解的集合)

证明:

我们对a1,a2……的关系分以下几种情况考虑:

1、a1>a2。   此时区间2包含区间1。这种情况下显然不会选择区间2,因为选择区间1会留下更多的剩余空间。

                      不仅区间2如此,以后所有区间中只要有一个 i 满足a1 > ai,i 都不要选。

                      即此种情况下,选择区间1是明智的,与策略一致。

2、排除情况1后,一定有a1<=a2<=a3……。


                      在此条件下,如图所示,不论区间1、2的相对位置如何,选择区间1都会为以后的选择留下更大的剩余空间。

                      从而在此种情况下, 因此选择区间1也是明智的,与策略一致。

经典例题:活动安排问题->http://acm.nyist.net/JudgeOnline/problem.php?pid=14

二、区间选点问题

数轴上有n个闭区间[ai,bi]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)。

贪心策略:

按照b1<=b2<=b3…(b相同时按a从大到小)的方式排序排序,从前向后遍历,当遇到没有加入集合的区间时,选取这个区间的右端点b。

证明:

为了方便起见,如果区间i内已经有一个点被取到,我们称区间i被满足。

1、首先考虑区间包含的情况,当小区间被满足时大区间一定被满足。所以我们应当优先选取小区间中的点,从而使大区间不用考虑。

      按照上面的方式排序后,如果出现区间包含的情况,小区间一定在大区间前面。所以此情况下我们会优先选择小区间。

      则此情况下,贪心策略是正确的。

2、排除情况1后,一定有a1<=a2<=a3……。

例题:http://acm.nyist.net/JudgeOnline/problem.php?pid=287

#include<iostream>#include<cstring>#include<cstdio>#include<algorithm>#include<cmath>using namespace std;const int MAX=10000+5;struct activity{    double a,b;};activity act[MAX];int cmp(activity x,activity y){    return x.b<y.b;}int main(){    int n;    double d;    int num=1;    while(scanf("%d%lf",&n,&d)!=EOF)    {        if(n==0 && d==0) break;        int flag=0;        for(int i=0;i<n;i++)        {            double x,y;            scanf("%lf%lf",&x,&y);            if(d<y) flag=1;            else            {                 double d1=sqrt(d*d-y*y);                 act[i].a=x-d1;                 act[i].b=x+d1;            }        }        if(flag)        {            printf("-1\n");      //      num++;            continue;        }        else        {            int cnt=0,end=-1;            sort(act,act+n,cmp);            for(int i=0;i<n;i++)            {                if(end<act[i].a)                {                    end=act[i].b;                    cnt++;                }            }            printf("Case %d: %d\n",num++,cnt);        }    }    return 0;}

三、区间覆盖问题

问题描述:给定一个长度为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],由于7最大,所以下一步选择[3,7],最后一步只能选择[6,8],这个时候刚好达到了8退出,所选区间为3

经典例题:喷水装置->http://acm.nyist.net/JudgeOnline/problem.php?pid=12

#include <iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;struct qujian{    double a,b;};qujian q[10000+5];int cmp(qujian x,qujian y){    return x.a<y.a;}int main(){    int t,n,w,h,r,x;    scanf("%d",&t);    while(t--)    {        scanf("%d%d%d",&n,&w,&h);        int m=0;        for(int i=0;i<n;i++)        {             scanf("%d%d",&x,&r);             double len=(double)(r*r)-(double)(h/2)*(h/2);             if(len>=0)             {                 double l=sqrt(len);                 if(x-l<0)q[m].a=0;                 else q[m].a=x-l;                 if(x+l>w) q[m].b=w;                 else q[m].b=x+l;                 m++;             }        }        sort(q,q+m,cmp);        int cnt=0,k=-1;        double start=0;  /*      for(int i=0;i<m;i++)            cout<<q[i].a<<" "<<q[i].b<<endl;  */        while(start<w && q[k+1].a<=start)        {            double mm=-1; //这是陷阱,不能设0            for(int i=k+1;i<m && q[i].a<=start;i++)            {                if(mm<q[i].b)                {                    mm=q[i].b;                    k=i;                }            }            start=mm;            cnt++;        }        if(start<w) printf("0\n");        else  printf("%d\n",cnt);    }    return 0;}




1 0