【编程之美】2.19 区间重合判断
题目: 给定一个目标区间[x,y]和N个无序的源区间[x1,y1] [x2,y2] ... [xn,yn],判断源区间[x,y]是不是在目标区间内。
思路:把源区间按x从小到大排序,再把区间依次合并,每完整得到一个合并区间后,判断[x,y]是否在合并区间内。
#include <stdio.h> #include <stdlib.h> typedef struct { int x; int y; }Region; int cmp(const void * a, const void * b) //由小到大排序 { return *(int *)a - *(int *)b; } bool isInRegion(Region dst, Region * src, int N) { qsort(src, N, sizeof(src[0]), cmp); //按输入的x 从小到大排序 Region currentRegion = src[0]; for(int i = 1; i < N; i++) { if(src[i].y <= currentRegion.y) //判断的区间已经包含在当前区间 { continue; } else { if(src[i].x > currentRegion.y) //出现了新的区间 与当前区间不交叠 { if(dst.x >= currentRegion.x && dst.y <= currentRegion.y) //若目标区间在已经截断的区间内 返回true { return true; } else //否则 把当前区间改成新出现的区间 { currentRegion = src[i]; } } else { currentRegion.y = src[i].y; } } } if(dst.x >= currentRegion.x && dst.y <= currentRegion.y) //若目标区间在已经截断的区间内 返回true { return true; } else { return false; } } int main() { Region src[3] = {{2,3},{1,2},{4,9}}; Region dst = {1,6}; bool b = isInRegion(dst, src, 3); return 0; }
看网上别人的答案。http://blog.csdn.net/tianshuai1111/article/details/7828961中的思路
先用区间的左边界值对目标区间进行排序O(nlogn),对排好序的区间进行合并O(n),对每次待查找的源区间,用二分查出其左右两边界点分别处于合并后的哪个源区间中O(logn),若属于同一个源区间则说明其在目标区间中,否则就说明不在。
感觉这个解法中虽然说查找处于合并后的哪个区间只要O(logn)但是必须要先把所有的区间合并完。我的方法虽然要对每一个合并区间判断,但是如果中间就找到了后面就不用做了。
#include <iostream> #include <algorithm> using namespace std; struct Line { int low, high; bool operator<(const Line &l) const {return low<l.low;} }; #define MAXN 10001 Line lines[MAXN]; // 目标区间 int ncnt = 0; // 合并后区间的个数 #define N 101 Line sl[N]; // 待查询的源区间 // 用二分查找找出key所在的区间,以区间的low作为划分 int GetIndex(int key) { int u, v; u = 0; v = ncnt-1; while (u<=v) // u,v可取等号 { int m = (u+v)>>1; if (key >= lines[m].low) u = m+1; else v = m-1; } return v; } int main() { int n, k, i, j; cin >> n >> k; // n是目标区间的个数,k是待查询的源区间的个数 for (i=0; i<n; i++) cin >> lines[i].low >> lines[i].high; for (i=0; i<k; i++) cin >> sl[i].low >> sl[i].high; // 排序O(nlogn) sort(lines, lines+n); // 合并O(n) int lasthigh = lines[0].high; for (i=1; i<n; i++) if (lasthigh >= lines[i].low) lasthigh = lines[i].high; else { lines[ncnt++].high = lasthigh; lines[ncnt].low = lines[i].low; lasthigh = lines[i].high; } lines[ncnt++].high = lasthigh; for (i=0; i<k; i++) { // 单词查找时间O(logn) int s1 = GetIndex(sl[i].low); int s2 = GetIndex(sl[i].high); if (s1==s2 && sl[i].high <= lines[s2].high) printf("Yes\n"); else printf("No\n"); } }
法二:使用并查集,对每个区间合并到一个子树上,最后判断源区间的x和y的根是否相同。
----------------------------------------------------------------------------------------------
补充并查集知识:
描述
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。 规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
Input
第一行:三个整数n,m,p,(n< =5000,m< =5000,p< =5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。 以下m行:每行两个数Mi,Mj,1< =Mi,Mj< =N,表示Mi和Mj具有亲戚关系。 接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
Output
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。
分析——问题实质
初步分析觉得本题是一个图论中判断两个点是否在同一个连通子图中的问题。对于题目中的样例,以人为点,关系为边,建立无向图如下:
比如判断3和4是否为亲戚时,我们检查3和4是否在同一个连通子图中,结果是在,于是他们是亲戚。又如7和10不在同一个连通子图中,所以他们不是亲戚。
用图的数据结构的最大问题是,我们无法存下多至(M=)2 000 000条边的图,后面关于算法时效等诸多问题就免谈了。
用图表示关系过于“奢侈”了。其实本题只是一个对分离集合(并查集)操作的问题。
我们可以给每个人建立一个集合,集合的元素值有他自己,表示最开始时他不知道任何人是它的亲戚。以后每次给出一个亲戚关系a, b,则a和他的亲戚与b和他的亲戚就互为亲戚了,将a所在集合与b所在集合合并。对于样例数据的操作全过程如下:
输入关系 分离集合
初始状态
(2,4) {2,4}
(5,7) {2,4} {5,7}
(1,3) {1,3} {2,4} {5,7}
(8,9) {1,3} {2,4} {5,7} {8,9}
(1,2) {1,2,3,4} {5,7} {8,9}
(5,6) {1,2,3,4} {5,6,7} {8,9}
(2,3) {1,2,3,4} {5,6,7} {8,9}
最后我们得到3个集合{1,2,3,4}, {5,6,7}, {8,9},于是判断两个人是否亲戚的问题就变成判断两个数是否在同一个集合中的问题。如此一来,需要的数据结构就没有图结构那样庞大了。
算法需要以下几个子过程:
(1) 开始时,为每个人建立一个集合SUB-Make-Set(x);
(2) 得到一个关系后a,b,合并相应集合SUB-Union(a,b);
(3) 此外我们还需要判断两个人是否在同一个集合中,这就涉及到如何标识集合的问题。我们可以在每个集合中选一个代表标识集合,因此我们需要一个子过程给出每个集合的代表元SUB-Find-Set(a)。于是判断两个人是否在同一个集合中,即两个人是否为亲戚,等价于判断SUB-Find-Set(a)=SUB-Find-Set(b)。
--------------------------------------------------------------------------------------------------------------
大神,方法二中 rank表示这个元素是几个层的代表元素, father表示区间的代表元素,如下,rank[1] = 3 rank [2] = 1 rank[6] = 2
1-9
/ | \
1-4 2-7 6-9
/ \
1-3 2-4
但是这个方法的一个重要问题是,如果只有两个区间 【1-10000000】【600000000-400000000000】这样数字很大的,那还要对数字中所有的元素循环,
不划算。
#include<iostream> using namespace std; const int size = 100; int father[size]; int rank[size]; void make_set(int n) { for(int i = 1; i <= n; i ++){ father[i] = i; rank[i] = 1; } } int find_set(int x)//寻找代表元素 { if(x != father[x]){ //元素不是单独的段,在某个区间内,返回某个区间代表 father[x] = find_set(father[x]); } return father[x]; } void Union(int x, int y) { x = find_set(x); y = find_set(y); if(x == y){ //两个在同一个区间 return ; } if(rank[x] < rank[y]){ father[x] = y; } else{ father[y] = x; if(rank[x] == rank[y]){ rank[x] ++; } } } int main() { int x1, y1; cin >> x1 >> y1;//输入要判断区间 int x, y; int n; cin >> n; //区间的个数 make_set(size); while(n --){ cin >> x >> y; //输入每个区间 if(x > y){//这一步很关键,表示考虑的周到 swap(x, y); } for(int i = x + 1; i <= y; i++){//将区间内的 段合并到已有区间 Union(x, i); } } if(find_set(x1) == find_set(y1)){ cout << "yes" << endl; } else{ cout << "no" << endl; } system("pause"); }
这道题后面还有个二维区间重合判断的,说是要用到线段树。还没看完,之后单独写吧。