线段树的学习之:如何用线段树计算矩形面积

    线段树是一种灵活的具有区间管理功能的数据结构,其用途跨越离散和线性思想!

我们最常用到的线段树就是区间求和:

 
 
  1. /* 
  2.  * InterverTree.cpp 
  3.  * 
  4.  *  Created on: 2012-11-1 
  5.  *      Author: Administrator 
  6.  */ 
  7. #include<stdio.h> 
  8. #define M 100 
  9. #define Mid(a,b) ((a)+(b))>>1 
  10. int a[11]={0,1,2,3,4,5,6,7,8,9,10}; 
  11. struct Tree{ 
  12.     int left,right; 
  13.     int key,sum; 
  14. }tree[M]; 
  15. /* 
  16.  * 建树 
  17.  * */ 
  18. void build(int id,int left,int right){ 
  19.     tree[id].left=left; 
  20.     tree[id].right=right; 
  21.     if(left==right){ 
  22.         tree[id].sum=tree[id].key=a[left]; 
  23.         return
  24.     } 
  25.     int mid=Mid(left,right); 
  26.     build(id*2,left,mid); 
  27.     build(id*2+1,mid+1,right); 
  28.     tree[id].sum=tree[id*2].sum+tree[id*2+1].sum; 
  29. /* 
  30.  * 更新点 
  31.  * */ 
  32. void update(int id,int pos,int key){ 
  33.     if(tree[id].left==tree[id].right){ 
  34.         tree[id].sum=tree[id].key=key; 
  35.         return ; 
  36.     } 
  37.     int mid=Mid(tree[id].left,tree[id].right); 
  38.  
  39.     pos<=mid?update(id*2,pos,key):update(id*2+1,pos,key); 
  40.  
  41.     tree[id].sum=tree[id*2].sum+tree[id*2+1].sum; 
  42. /* 
  43.  * 查询区间 
  44.  * */ 
  45. int query(int id,int left,int right){ 
  46.     if(tree[id].left==left&&tree[id].right==right){ 
  47.         return tree[id].sum; 
  48.     } 
  49.     int mid=Mid(tree[id].left,tree[id].right); 
  50.  
  51.     if(right<=mid){ 
  52.         return query(id*2,left,right); 
  53.     }else if(left>mid){ 
  54.         return query(id*2+1,left,right); 
  55.     }else
  56.         return query(id*2,left,mid)+query(id*2+1,mid+1,right); 
  57.     } 
  58. int main(){ 
  59.     build(1,1,10);//利用a数组中的元素建树 ,0号元素不用 
  60.     int ans=query(1,1,5);//查询区间[1,5]元素的和 
  61.     printf("sum=%d\n",ans); 
  62.     update(1,5,0);//更新5号位置的点的值 
  63.     ans=query(1,1,10); 
  64.     printf("sum=%d\n",ans); 
  65.     return 0; 

    像上面这样是最最基础的线段树,典型的用空间换时间的做法!而且线段树也继承了树形数据结构一惯的特点:把查询复杂度从O(n)降低到log(n)。

    但是通常用线段树,我们不会用得这么直白,会加一些技巧,比如懒操作!懒操作通常用在什么地方呢?比如更新区间!更新区间的时候,我们一般不会直接去傻呼呼地去更新每一个点,而是把需要更新的区间先记录起来,然后如果要用到区间的子区间的时候,我们才去真正更新!

 
 
  1. /* 
  2.  * InterverTree.cpp 
  3.  * 
  4.  *  Created on: 2012-11-1 
  5.  *      Author: Administrator 
  6.  */ 
  7. #include<stdio.h> 
  8. #define M 100 
  9. #define Mid(a,b) ((a)+(b))>>1 
  10. int a[11]={0,1,2,3,4,5,6,7,8,9,10}; 
  11. struct Tree{ 
  12.     int left,right; 
  13.     int sum,add; 
  14. }tree[M]; 
  15. /* 
  16.  * 建树 
  17.  * */ 
  18. void build(int id,int left,int right){ 
  19.     tree[id].left=left; 
  20.     tree[id].right=right; 
  21.     if(left==right){ 
  22.         tree[id].sum=a[left]; 
  23.         tree[id].add=0; 
  24.         return
  25.     } 
  26.     int mid=Mid(left,right); 
  27.     build(id*2,left,mid); 
  28.     build(id*2+1,mid+1,right); 
  29.     tree[id].sum=tree[id*2].sum+tree[id*2+1].sum; 
  30.  
  31. void push_down(int id){ 
  32.     if(tree[id].add>0){ 
  33.         tree[id].sum+=(tree[id].right-tree[id].left+1)*tree[id].add; 
  34.         if(tree[id].left<tree[id].right){//非叶子结点 
  35.             tree[id*2].add+=tree[id].add; 
  36.             tree[id*2+1].add+=tree[id].add; 
  37.         } 
  38.         tree[id].add=0; 
  39.     } 
  40. /* 
  41.  * 更新点 
  42.  * */ 
  43. void update(int id,int pos,int key){ 
  44.     push_down(id); 
  45.     if(tree[id].left==tree[id].right){ 
  46.         tree[id].sum=key; 
  47.         return ; 
  48.     } 
  49.     int mid=Mid(tree[id].left,tree[id].right); 
  50.  
  51.     pos<=mid?update(id*2,pos,key):update(id*2+1,pos,key); 
  52.  
  53.     tree[id].sum=tree[id*2].sum+tree[id*2+1].sum; 
  54. /* 
  55.  * 更新区间:把区间[left,right]同时增加key 
  56.  * */ 
  57. void update_add(int id,int left,int right,int key){ 
  58.     push_down(id); 
  59.     if(tree[id].left==left&&tree[id].right==right){//找到操作区间了 
  60.         tree[id].add=key; 
  61.         return
  62.     } 
  63.     int mid=Mid(tree[id].left,tree[id].right); 
  64.     if(right<=mid){ 
  65.         update_add(id*2,left,right,key); 
  66.     }else if(left>mid){ 
  67.         update_add(id*2+1,left,right,key); 
  68.     }else
  69.         update_add(id*2,left,mid,key); 
  70.         update_add(id*2+1,mid+1,right,key); 
  71.     } 
  72. /* 
  73.  * 查询区间 
  74.  * */ 
  75. int query(int id,int left,int right){ 
  76.     push_down(id); 
  77.     if(tree[id].left==left&&tree[id].right==right){ 
  78.         return tree[id].sum; 
  79.     } 
  80.     int mid=Mid(tree[id].left,tree[id].right); 
  81.     if(right<=mid){ 
  82.         return query(id*2,left,right); 
  83.     }else if(left>mid){ 
  84.         return query(id*2+1,left,right); 
  85.     }else
  86.         return query(id*2,left,mid)+query(id*2+1,mid+1,right); 
  87.     } 
  88. int main(){ 
  89.     build(1,1,10);//利用a数组中的元素建树 ,0号元素不用 
  90.     int ans=query(1,1,5);//查询区间[1,5]元素的和 
  91.     printf("sum=%d\n",ans); 
  92.     update(1,6,10); 
  93.     //懒操作 
  94.     update_add(1,1,5,10); 
  95.     ans=query(1,1,6); 
  96.     printf("ans=%d\n",ans); 
  97.     return 0; 

    这也是线段树的基础操作,一般初学线段树容易进入一个误区,那就是线段树只能对数组进行操作,而且线段树建树也一定是build(id*2,left,mid),build(id*2+1,mid+1,right); 如果这样来理解线段树的话,就大错特错了!

   线段树是一种数据结构,而数据结构是我们生活中现实的事物极度抽象的结晶!我们要做的就是学习分析事物,透过现象看本质。这一点说起来很容易,做起来却极难!

虽然难,但是我们还是要做!那就是线段树在几何上的应用。说得更直接一点:

我们怎么把一条线段用线段树来表示!比如线段[1,5]怎么用线段树来表示:还是像前面建树那样:[1,1] [2,2] [3,3] [4,4] [5,5]? 那是行不通嘀!

我们这样建树:[1,2][2,3][3,4][4,5],这里面就涉及到一个元线段的概念!

    而且如果线段的长度很大,或者线段是浮点型数据,那么我们就需要对线段进行离散化! 离散化的方法也很简单!比如有三条线段:1.1-2.2   3.3-5.5  8.8-1000.1 我们就可以用一个长度为6的数组存储起来。然后用下标加key的方式来建树(后面的源代码中会展示出来)。这样的话,一条线段就离散化出来了!

    思路分析出来后,可以用一道题来加深一下领悟:线段树水题poj1151

参考博客:http://blog.csdn.net/tsaid/article/details/6706893

 

 
 
  1. #include<stdio.h> 
  2. #include<algorithm> 
  3. using namespace std; 
  4. #define M 1000 
  5. #define eps 1e-8 
  6. struct Tree{ 
  7.     int ll,rr,cover;//这里的cover之所以用int而不用bool是因为可能出现多条边重叠 
  8.     double lf,rf; 
  9.     double len; 
  10. }tree[4*M]; 
  11. struct Line{ 
  12.     double x,y1,y2; 
  13.     int flag; 
  14. }line[M]; 
  15. double y[M];//用来离散化y坐标轴 
  16. int cmp_double(const double &d1,const double &d2){ 
  17.     double d=d1-d2; 
  18.     if(d>eps)//d1>d2 
  19.         return 1; 
  20.     else if(d<-eps)//d1<d2 
  21.         return -1; 
  22.     else //d1==d2 
  23.         return 0; 
  24. //比较函数 
  25. bool cmp(const Line &l1,const Line &l2){//排序的比较函数 
  26.         return l1.x<l2.x; 
  27. //建树 
  28. void build(int id,int ll,int rr){ 
  29.     tree[id].ll=ll;tree[id].rr=rr; 
  30.     tree[id].lf=y[ll],tree[id].rf=y[rr]; 
  31.     tree[id].len=tree[id].cover=0; 
  32.     if(ll+1==rr)return;//如果已经是元线段,则不用拆分了 
  33.     int mid=(ll+rr)>>1; 
  34.     build(id*2,ll,mid); 
  35.     build(id*2+1,mid,rr); 
  36. void lenght(int id){//求用于计算的线段实际长度 
  37.     if(tree[id].cover>0){ 
  38.         tree[id].len=tree[id].rf-tree[id].lf; 
  39.     }else if(tree[id].ll+1==tree[id].rr){//元线段 
  40.         tree[id].len=0; 
  41.     }else 
  42.         tree[id].len=tree[id*2].len+tree[id*2+1].len; 
  43. //更新树 
  44. void update(int id,Line line){ 
  45.     if(cmp_double(tree[id].lf,line.y1)==0&&cmp_double(tree[id].rf,line.y2)==0){ 
  46.         tree[id].cover+=line.flag; 
  47.         lenght(id); 
  48.         return
  49.         //这里的比较有点特别,值得关注一下, 
  50.         // 由于是浮点型的数据,所以不能直接用mid来比较 
  51.  
  52.     }else if(cmp_double(line.y1,tree[2*id+1].lf)>=0){//用右孩子的左边界来比较 
  53.         update(id*2+1,line); 
  54.     }else if(cmp_double(line.y2,tree[id*2].rf)<=0){// 左孩子的右边界来比较 
  55.         update(id*2,line); 
  56.     }else{//分跨两个边界 
  57.         Line tmp; 
  58.         tmp=line;tmp.y2=tree[id*2].rf; 
  59.         update(2*id,tmp); 
  60.         tmp=line;tmp.y1=tree[id*2+1].lf; 
  61.         update(2*id+1,tmp); 
  62.     } 
  63.     lenght(id);//回溯的时候 修改,使根结点的len实时更新 
  64. int main(){ 
  65.     int n; 
  66.     int i,t,ti=0; 
  67.     while(scanf("%d",&n)&&n){ 
  68.         double x1,y1,x2,y2; 
  69.         for(t=i=1;i<=n;i++){ 
  70.             scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2); 
  71.             line[t].x=x1; 
  72.             line[t].y1=y1; 
  73.             line[t].y2=y2; 
  74.             line[t].flag=1; 
  75.             y[t]=y1; 
  76.             t++; 
  77.             line[t].x=x2; 
  78.             line[t].y1=y1; 
  79.             line[t].y2=y2; 
  80.             line[t].flag=-1; 
  81.             y[t]=y2; 
  82.             t++; 
  83.         } 
  84.         sort(line+1,line+t,cmp); 
  85.         sort(y+1,y+t);//线段的离散化 
  86.  
  87.         build(1,1,t-1); 
  88.         update ( 1, line[1] );//第一条边一定是入边 
  89.         double ans=0.00; 
  90.         for(i=2;i<t;i++){ 
  91.             ans+=tree[1].len*(line[i].x-line[i-1].x); 
  92.             update(1,line[i]);//最后一条边肯定是出边,不用考虑 
  93.         } 
  94.         printf("Test case #%d\n",++ti); 
  95.         printf("Total explored area: %.2lf\n\n",ans); 
  96.  
  97.     } 
  98.     return 0; 

       此外,矩形分割也可以求解矩形面积相关的题!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值