/*
区间重合判断:
给定一个源区间[x,y](y>=x)和N个无序的目标区间[x1,y1],[x2,y2],[x3,y3],...,[xn,yn],判断源区间[x,y]是不是在目标区间内(也即[x,y]是否属于任意[xi,yi])
例如:给定源区间[1,6]和一组无序的目标区间[2,3][1,2][3,9],即可认为区间[1,6]在区间[2,3][1,2][3,9]内(因为目标区间实际上是[1,9])
解法1:
本质上将源区间上不断减去给定的目标区间,如果减完,说明完全覆盖
本质在于目标区间的处理。直接的思路即将源区间[x,y](y>=x)和N个无序的目标区间[x1,y1][x2,y2]...[xn,yn]逐个投影到坐标轴上,只考察源区间未被覆盖的部分
如果所有的目标区间全部投影完毕,仍然有源区间没有被覆盖,那么源区间就不在目标区间之内。
源区间[1,6],那么最初未被覆盖的部分为{[1,6]},将按照顺序考察目标区间[2,3][1,2][3,9]。
将目标区间[2,3]投影到坐标轴,那么未被覆盖的部分为{[1,6]将变成{[1,2],[3,6]}
将目标区间[1,2]投影到坐标轴,那么未被覆盖的部分为{[3,6]},将目标区间[3,9]投影到坐标轴,那么未被覆盖的部分为空。
每次操作,尚未被覆盖的区间数组大小最多增加1,每投影一个新区间,计算哪些源区间数组被覆盖需要O(log2N)的时间复杂度,但是更新尚未被覆盖的区间数组
需要O(N)的时间复杂度,所以总的时间复杂度为O(N^2)
解法2:
对现有的数组进行预处理(如排序和合并),将无序的目标区间合并成几个有序的目标区间,进行区间之间的比较。
先合并,即先将目标区间数组按照X轴坐标从小到大排序,如[2,3][1,2][3,9] ->[1,2][2,3][3,9];接着扫描排序后的目标区间数组,将这些区间合并成若干个不相交
的区间,如[1,2][2,3][3,9]->[1,9]
然后用二分查找,判定源区间[x,y]是否被合并后的这些互不相交的区间中的某一个包含。如[1,6]被[1,9]包含,则可以说明源区间在目标区间之内。
排序:O(nLogN)
合并:O(N)
查找:log2N
所以总的时间复杂度为)(N*log2N + k*log2N),k为查询的次数,合并目标区间数组的初始化操作只需要进行一次
输入:
3(多少个比较区间)
1 6 (源区间)
2 3 (目标区间)
1 2
3 9
3
1 6
2 3
1 2
4 9
4
1 6
1 3
1 2
2 4
5 9
输出:
Yes
No
*/
/*
关键:
1 对现有的数组进行预处理(如排序和合并),将无序的目标区间合并成几个有序的目标区间,进行区间之间的比较。
先合并,即先将目标区间数组按照X轴坐标从小到大排序,如[2,3][1,2][3,9] ->[1,2][2,3][3,9];接着扫描排序后的目标区间数组,将这些区间合并成若干个不相交
的区间,如[1,2][2,3][3,9]->[1,9]
然后用二分查找,判定源区间[x,y]是否被合并后的这些互不相交的区间中的某一个包含。
2 for(int i = 1 ; i < iLen ; i++)//归并区间的大体算法参见删除指定字符的算法,向前归并,我们可以采用如果可以归并,就一直归并,而不是两两归并的方法
{
if(iEnd >= secArr[i]._iBeg)//可以归并
{
if(iEnd <= secArr[i]._iEnd)//非完全重合,终点选取后一个区间的终点
{
iEnd = secArr[i]._iEnd;
}
else//完全重合,终点仍然是前一个区间的终点,不变
{
}
if( i == iLen -1)//注意,如果这个时候i已经抵达末尾,需要立即归并
{
secArr[iCnt++].set(iBeg,iEnd);
iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
iEnd = secArr[i]._iEnd;
}
}
else//不可以归并时,这之后产生新的区间,并且向后继续归并。
{
secArr[iCnt++].set(iBeg,iEnd);
iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
3 int lower_bound(Section* secArr,int low,int high,const Section& secOri)
//查找原区间在目标区间中的位置,关键是要找到一个区间,目标区间的起点<=源区间的起点,目标区间的终点>=源区间的终点,我们可以采用二分搜索,通过起点
//值来来作为搜索条件,实际上是lower_bound的一个变体,如果能够找到时,比较终点,若目标区间终点<源区间终点,不是寻找前面一个区间(如果存在的话)
//能否包含,如果能包含就可以,如果这两个区间都不能包含,那就是错误的了。这里lower_bound返回的下标要么是起点都相同的区间的下标,要么是排序后
//比源区间大一号的区间下标,所以这里要比较两次
*/
#include <stdio.h>
#include <algorithm>
using namespace std;
const int MAXSIZE = 10000;
typedef struct Section
{
int _iBeg;
int _iEnd;
bool operator < (const Section& sec) const
{
if(_iBeg != sec._iBeg)
{
return _iBeg < sec._iBeg;
}
else
{
return _iEnd < sec._iEnd;
}
}
bool operator == (const Section& sec) const
{
if(_iBeg == sec._iBeg)//只要起始区间相同,就认为相同
{
return true;
}
else
{
return false;
}
}
void set(int iBeg,int iEnd)
{
_iBeg = iBeg;
_iEnd = iEnd;
}
}Section;
bool canMerge(const Section& secFront,const Section& secBack)//判断前面的区间是否与后面的区间能够归并
{
if(secFront._iEnd < secBack._iBeg || secFront._iBeg > secBack._iEnd)//A区间的后面比B区间的前面都要小,或A区间的前面比B区间的后面都要大,不能合并
{
return false;
}
else
{
return true;
}
}
int max(int a,int b)
{
return a > b ? a : b;
}
int mergeSection(Section* secArr,int iLen)
{
int iCnt = 0;
int iEnd = secArr[0]._iEnd;
int iBeg = secArr[0]._iBeg;
for(int i = 1 ; i < iLen ; i++)//归并区间的大体算法参见删除指定字符的算法,向前归并,我们可以采用如果可以归并,就一直归并,而不是两两归并的方法
{
if(iEnd >= secArr[i]._iBeg)//可以归并
{
if(iEnd <= secArr[i]._iEnd)//非完全重合,终点选取后一个区间的终点
{
iEnd = secArr[i]._iEnd;
}
else//完全重合,终点仍然是前一个区间的终点,不变
{
}
//if( i == iLen -1)//注意,如果这个时候i已经抵达末尾,需要立即归并
//{
// secArr[iCnt++].set(iBeg,iEnd);
// iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
// iEnd = secArr[i]._iEnd;
//}
}
else//不可以归并时,这之后产生新的区间,并且向后继续归并。
{
secArr[iCnt++].set(iBeg,iEnd);
iBeg = secArr[i]._iBeg;//重新设置iBeg,iEnd为当前区间的起点和终点
iEnd = secArr[i]._iEnd;
}
}
secArr[iCnt++].set(iBeg,iEnd);//漏掉了对最后一次区间的归并
return iCnt;//返回归并后区间总数
}
/*
lower_bound:当iVal > iArr[mid]时,mid是不可能的,low = mid+1,
当iVal < iArr[mid]时,mid是可能,high = mid
当iVal = iArr[mid]时,mid是肯能的,但是需要向前寻找,high = mid
综上:iVal > iArr[mid],low = mid + 1
else,high = mid
*/
int lower_bound(Section* secArr,int low,int high,const Section& secOri)
//查找原区间在目标区间中的位置,关键是要找到一个区间,目标区间的起点<=源区间的起点,目标区间的终点>=源区间的终点,我们可以采用二分搜索,通过起点
//值来来作为搜索条件,实际上是lower_bound的一个变体,如果能够找到时,比较终点,若目标区间终点<源区间终点,不是寻找前面一个区间(如果存在的话)
//能否包含,如果能包含就可以,如果这两个区间都不能包含,那就是错误的了。这里lower_bound返回的下标要么是起点都相同的区间的下标,要么是排序后
//比源区间大一号的区间下标,所以这里要比较两次
{
int mid;
while(low < high)
{
mid = low + (high - low)/2;
if(secArr[mid]._iBeg < secOri._iBeg)//直接用起始点去比较
{
low = mid + 1;
}
else
{
high = mid;
}
}
return low;
}
bool isContain(const Section& secFront,const Section& secBack)//判断后面一个区间是否完全包含令一个区间
{
if(secBack._iBeg <= secFront._iBeg && secBack._iEnd >= secFront._iEnd)
{
return true;
}
else
{
return false;
}
}
void process()
{
int n;
while(EOF != scanf("%d",&n))
{
Section secOri;
scanf("%d %d",&secOri._iBeg,&secOri._iEnd);
Section secGoal[MAXSIZE];
for(int i = 0 ; i < n ; i++)
{
scanf("%d %d",&secGoal[i]._iBeg,&secGoal[i]._iEnd);
}
sort(secGoal,secGoal + n);//排序
int iSize = mergeSection(secGoal,n);//归并能归并的区间,必须满足能被包含的条件,而且归并的必定是前后两个相邻的区间
int iIndex = lower_bound(secGoal,0,iSize,secOri);//二分查找
if(isContain(secOri,secGoal[iIndex]) || (iIndex-1 >= 0 && isContain(secOri,secGoal[iIndex-1]) ) )
{
printf("Yes\n");
}
else
{
printf("No\n");
}
}
}
int main(int argc,char* argv[])
{
process();
getchar();
return 0;
}
编程之美:第二章 数字之魅 2.19区间重合判断
最新推荐文章于 2021-02-12 19:26:59 发布