编程之美2.19——区间重合判断(线段树)

来源:互联网 发布:淘宝卖家开通直播条件 编辑:程序博客网 时间:2024/05/01 07:46

问题:

1. 给定一个源区间[x,y]和N个无序的目标区间[x1,y1] [x2,y2] ... [xn,yn],判断源区间[x,y]是不是在目标区间内。

2. 给定一个窗口区域和系统界面上的N个窗口,判断这个窗口区域是否被已有的窗口覆盖。


1. 解法:

先用区间的左边界值对目标区间进行排序O(nlogn),对排好序的区间进行合并O(n),对每次待查找的源区间,用二分查出其左右两边界点分别处于合并后的哪个源区间中O(logn),若属于同一个源区间则说明其在目标区间中,否则就说明不在。

[cpp] view plaincopy
  1. <span style="font-size:18px;">#include <iostream>  
  2. #include <algorithm>  
  3. using namespace std;  
  4.   
  5. struct Line  
  6. {  
  7.     int low, high;  
  8.     bool operator<(const Line &l) const  
  9.     {return low<l.low;}  
  10. };  
  11.   
  12. #define MAXN 10001  
  13. Line lines[MAXN];   // 目标区间  
  14. int ncnt = 0;       // 合并后区间的个数  
  15.   
  16. #define N 101  
  17. Line sl[N];         // 待查询的源区间  
  18.   
  19. // 用二分查找找出key所在的区间,以区间的low作为划分  
  20. int GetIndex(int key)  
  21. {  
  22.     int u, v;  
  23.     u = 0; v = ncnt-1;  
  24.     while (u<=v) // u,v可取等号  
  25.     {  
  26.         int m = (u+v)>>1;  
  27.         if (key >= lines[m].low)  
  28.             u = m+1;  
  29.         else  
  30.             v = m-1;  
  31.     }  
  32.     return v;  
  33. }  
  34.   
  35. int main()  
  36. {  
  37.     int n, k, i, j;  
  38.     cin >> n >> k;  // n是目标区间的个数,k是待查询的源区间的个数  
  39.     for (i=0; i<n; i++)  
  40.         cin >> lines[i].low >> lines[i].high;  
  41.     for (i=0; i<k; i++)  
  42.         cin >> sl[i].low >> sl[i].high;  
  43.     // 排序O(nlogn)  
  44.     sort(lines, lines+n);  
  45.     // 合并O(n)  
  46.     int lasthigh = lines[0].high;  
  47.     for (i=1; i<n; i++)  
  48.         if (lasthigh >= lines[i].low)  
  49.             lasthigh = lines[i].high;  
  50.         else  
  51.         {  
  52.             lines[ncnt++].high = lasthigh;  
  53.             lines[ncnt].low = lines[i].low;  
  54.             lasthigh = lines[i].high;  
  55.         }  
  56.     lines[ncnt++].high = lasthigh;  
  57.     for (i=0; i<k; i++)  
  58.     {  
  59.         // 单词查找时间O(logn)  
  60.         int s1 = GetIndex(sl[i].low);  
  61.         int s2 = GetIndex(sl[i].high);  
  62.         if (s1==s2 && sl[i].high <= lines[s2].high)  
  63.             printf("Yes\n");  
  64.         else  
  65.             printf("No\n");  
  66.     }  
  67. }</span>  

2. 解法:

这个问题适合使用线段树来解答,单次查找的时间复杂度为O(nlogn),当然也能用数组解答,但单次查找的时间复杂度会增加到O(n^2)。这里我们直接使用线段树来解答。

线段树是一棵二叉树,将数轴划分成一系列的初等区间[I, I+1] (I=1,2,..,N-1)。每个初等区间对应于线段树的一个叶结点。线段树的内部结点对应于形如[ I,  J ](J – I > 1)的一般区间。由于线段树给每一个区间都分配了结点,利用线段树可以求区间并后的总长度与区间并后的线段数。先给出测试数据(前4行是系统界面上已有的N个窗口,之后的一行是待测试的窗口区域),后面是代码:

4
-15 0 5 10
-5 8 20 25
15 -4 24 14
0 -6 16 4

2 15 10 22

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include <cmath>  
  3. #include <algorithm>  
  4.   
  5. using namespace std;  
  6.   
  7. // 线段树的结点  
  8. struct SegNode  
  9. {  
  10.   int low, high;    // 线段的两端点索引  
  11.   int ncover;   // 线段被覆盖的次数  
  12.   SegNode *left;    // 结点的左子树  
  13.   SegNode *right;   // 结点的右子树  
  14.   SegNode() {low=high=0;ncover=0;  
  15.     left=right=NULL;}  
  16. };  
  17.   
  18. // 构造线段树,它是一个完全二叉树  
  19. void BuildSegTree(SegNode *&tree, int *index, int low, int high)  
  20. {  
  21.   if (low < high)  
  22.   {  
  23.     tree = new SegNode;  
  24.     tree->low = low;  
  25.     tree->high = high;  
  26.     if (high-low>1)  
  27.     {  
  28.       int m = (low+high)/2;  
  29.       BuildSegTree(tree->left, index, low, m);  
  30.       BuildSegTree(tree->right, index, m, high);  
  31.     }  
  32.   }  
  33. }  
  34.   
  35. // 往线段树中插入线段,即用线段(low,high)来覆盖线段树  
  36. void InsertSegTree(SegNode *tree, int low, int high)  
  37. {  
  38.   // 先序遍历  
  39.   if (low<=tree->low && tree->high<=high)  
  40.     tree->ncover++;  
  41.   else if (tree->high-tree->low > 1)  
  42.   {  
  43.     int m = (tree->low+tree->high)/2;  
  44.     if (low < m) InsertSegTree(tree->left, low, high);  
  45.     if (m < high) InsertSegTree(tree->right, low, high);  
  46.   }  
  47. }  
  48.   
  49. // 从线段树中删除线段  
  50. void DeleteSegTree(SegNode *tree, int low, int high)  
  51. {  
  52.   if (low<=tree->low && tree->high<=high)  
  53.     tree->ncover--;  
  54.   else if (tree->high-tree->low > 1)  
  55.   {  
  56.     int m = (tree->low+tree->high)/2;  
  57.     if (low < m) DeleteSegTree(tree->left, low, high);  
  58.     if (m < high) DeleteSegTree(tree->right, low, high);  
  59.   }  
  60. }  
  61.   
  62. // 线段树中是否包含线段(low,high)  
  63. bool FindSegTree(SegNode *tree, int low, int high)  
  64. {  
  65.     // 若当前区间被覆盖,且线段(low,high)属于当前区间则返回覆盖  
  66.     if (tree->ncover && tree->low <= low && high <= tree->high )  
  67.         return true;  
  68.     // 若(low,high)没被当前区间覆盖,则将其分为两段,  
  69.     // 分别考虑是否被子结点表示的区间覆盖  
  70.     else if (tree->high - tree->low > 1)  
  71.     {  
  72.         int m = (tree->low + tree->high) >> 1;  
  73.         bool ret = true;  
  74.         if (low<m) ret = FindSegTree(tree->left, low, high<m?high:m);  
  75.         if (!ret)  return false;  
  76.         if (m<high) ret = FindSegTree(tree->right, m<low?low:m, high);  
  77.         if (!ret)  return false;  
  78.         return true;  
  79.     }  
  80.     return false;  
  81. }  
  82.   
  83. #define LEFT  true  
  84. #define RIGHT false  
  85. #define INF 10000  
  86.   
  87. // 表示竖直方向的线段  
  88. struct Line  
  89. {  
  90.   int starty, endy; // 竖线的长度  
  91.   int x;        // 竖线的位置  
  92.   bool inout;   // 竖线是长方形的左边还是右边  
  93.   bool operator<(const Line& a) const{   // 依据x坐标进行排序  
  94.     return x<a.x;  
  95.   }  
  96. };  
  97.   
  98. // 所有竖直方向的线段  
  99. Line lines[INF];  
  100. // 对横向超元线段进行分组  
  101. int index[INF];  
  102. int nCnt = 0;  
  103.   
  104. // 获取key的位置  
  105. int GetIndex(int key)  
  106. {  
  107.     // 用二分查找查出key在index中的位置  
  108.     return lower_bound(index,index+nCnt,key)-index;  
  109. }  
  110.   
  111. // 获取key的位置或比它小的最大数的位置  
  112. int GetLower(int key)  
  113. {  
  114.     size_t pos = lower_bound(index,index+nCnt,key)-index;  
  115.     if (key == index[pos]) return pos;  
  116.     else return pos-1;  
  117. }  
  118.   
  119. // 获取key的位置或比它大的最小数的位置  
  120. int GetUpper(int key)  
  121. {  
  122.     return lower_bound(index,index+nCnt,key)-index;  
  123. }  
  124.   
  125. int main()  
  126. {  
  127.   int nRec;  
  128.   cin >> nRec;  
  129.   int i, j;  
  130.   int x[2], y[2];  
  131.   // 读取nRec个窗口的数据  
  132.   for (i=0; i<nRec; i++)  
  133.   {  
  134.     cin >> x[0] >> y[0] >> x[1] >> y[1];  
  135.     // 记录每个长方形的两条竖直边  
  136.     lines[2*i].x=x[0];      lines[2*i+1].x=x[1];  
  137.     lines[2*i].starty=lines[2*i+1].starty=min(y[0],y[1]);  
  138.     lines[2*i].endy=lines[2*i+1].endy=max(y[0],y[1]);  
  139.     lines[2*i].inout=LEFT;  lines[2*i+1].inout=RIGHT;  
  140.     // 对竖直的线段进行离散化  
  141.     index[2*i]=y[0];        index[2*i+1]=y[1];  
  142.   }  
  143.   // 待查询的窗口区域  
  144.   Line search[2];  
  145.   cin >> x[0] >> y[0] >> x[1] >> y[1];  
  146.   search[0].x=x[0];         search[1].x=x[1];  
  147.   search[0].starty=search[1].starty=min(y[0],y[1]);  
  148.   search[0].endy=search[1].endy=max(y[0],y[1]);  
  149.   search[0].inout=LEFT;     search[1].inout=RIGHT;  
  150.   // 对x坐标进行排序O(nlogn)  
  151.   sort(index, index+2*nRec);  
  152.   sort(lines, lines+2*nRec);  
  153.   // 排除index数组中的重复数据O(n)  
  154.   for (i=1; i<2*nRec; i++)  
  155.     if (index[i]!=index[i-1])  
  156.       index[nCnt++] = index[i-1];  
  157.   index[nCnt++] = index[2*nRec-1];  
  158.   // 建立线段树  
  159.   SegNode *tree;  
  160.   BuildSegTree(tree, index, 0, nCnt-1);  
  161.   // 单词查找的时间复杂度为O(nlogn)  
  162.   bool res;  
  163.   InsertSegTree(tree, GetIndex(lines[0].starty), GetIndex(lines[0].endy));  
  164.   for (i=1; i<2*nRec; i++)  
  165.   {  
  166.     if (lines[i].inout==LEFT)   // 遇窗口的左边界,将其加入线段树  
  167.       InsertSegTree(tree, GetIndex(lines[i].starty), GetIndex(lines[i].endy));  
  168.     else                        // 遇窗口的右边界,将其删出线段树  
  169.       DeleteSegTree(tree, GetIndex(lines[i].starty), GetIndex(lines[i].endy));  
  170.     if (lines[i].x!=lines[i-1].x && search[0].x < lines[i+1].x && search[1].x > lines[i].x)  
  171.     {  
  172.         // 从待查窗口区域的左边界开始查询直到其右边界结束查询  
  173.         res = FindSegTree(tree, GetLower(search[0].starty), GetUpper(search[0].endy));  
  174.         if (!res) break;  
  175.     }else if (search[1].x <= lines[i].x)  
  176.         break;  
  177.   }  
  178.   if (res) printf("Yes\n");  
  179.   else  printf("No\n");  
  180.   return 0;  
  181. }  
更多0
0 0
原创粉丝点击