线段树是一种灵活的具有区间管理功能的数据结构,其用途跨越离散和线性思想!
我们最常用到的线段树就是区间求和:
- /*
- * InterverTree.cpp
- *
- * Created on: 2012-11-1
- * Author: Administrator
- */
- #include<stdio.h>
- #define M 100
- #define Mid(a,b) ((a)+(b))>>1
- int a[11]={0,1,2,3,4,5,6,7,8,9,10};
- struct Tree{
- int left,right;
- int key,sum;
- }tree[M];
- /*
- * 建树
- * */
- void build(int id,int left,int right){
- tree[id].left=left;
- tree[id].right=right;
- if(left==right){
- tree[id].sum=tree[id].key=a[left];
- return;
- }
- int mid=Mid(left,right);
- build(id*2,left,mid);
- build(id*2+1,mid+1,right);
- tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
- }
- /*
- * 更新点
- * */
- void update(int id,int pos,int key){
- if(tree[id].left==tree[id].right){
- tree[id].sum=tree[id].key=key;
- return ;
- }
- int mid=Mid(tree[id].left,tree[id].right);
- pos<=mid?update(id*2,pos,key):update(id*2+1,pos,key);
- tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
- }
- /*
- * 查询区间
- * */
- int query(int id,int left,int right){
- if(tree[id].left==left&&tree[id].right==right){
- return tree[id].sum;
- }
- int mid=Mid(tree[id].left,tree[id].right);
- if(right<=mid){
- return query(id*2,left,right);
- }else if(left>mid){
- return query(id*2+1,left,right);
- }else{
- return query(id*2,left,mid)+query(id*2+1,mid+1,right);
- }
- }
- int main(){
- build(1,1,10);//利用a数组中的元素建树 ,0号元素不用
- int ans=query(1,1,5);//查询区间[1,5]元素的和
- printf("sum=%d\n",ans);
- update(1,5,0);//更新5号位置的点的值
- ans=query(1,1,10);
- printf("sum=%d\n",ans);
- return 0;
- }
像上面这样是最最基础的线段树,典型的用空间换时间的做法!而且线段树也继承了树形数据结构一惯的特点:把查询复杂度从O(n)降低到log(n)。
但是通常用线段树,我们不会用得这么直白,会加一些技巧,比如懒操作!懒操作通常用在什么地方呢?比如更新区间!更新区间的时候,我们一般不会直接去傻呼呼地去更新每一个点,而是把需要更新的区间先记录起来,然后如果要用到区间的子区间的时候,我们才去真正更新!
- /*
- * InterverTree.cpp
- *
- * Created on: 2012-11-1
- * Author: Administrator
- */
- #include<stdio.h>
- #define M 100
- #define Mid(a,b) ((a)+(b))>>1
- int a[11]={0,1,2,3,4,5,6,7,8,9,10};
- struct Tree{
- int left,right;
- int sum,add;
- }tree[M];
- /*
- * 建树
- * */
- void build(int id,int left,int right){
- tree[id].left=left;
- tree[id].right=right;
- if(left==right){
- tree[id].sum=a[left];
- tree[id].add=0;
- return;
- }
- int mid=Mid(left,right);
- build(id*2,left,mid);
- build(id*2+1,mid+1,right);
- tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
- }
- void push_down(int id){
- if(tree[id].add>0){
- tree[id].sum+=(tree[id].right-tree[id].left+1)*tree[id].add;
- if(tree[id].left<tree[id].right){//非叶子结点
- tree[id*2].add+=tree[id].add;
- tree[id*2+1].add+=tree[id].add;
- }
- tree[id].add=0;
- }
- }
- /*
- * 更新点
- * */
- void update(int id,int pos,int key){
- push_down(id);
- if(tree[id].left==tree[id].right){
- tree[id].sum=key;
- return ;
- }
- int mid=Mid(tree[id].left,tree[id].right);
- pos<=mid?update(id*2,pos,key):update(id*2+1,pos,key);
- tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
- }
- /*
- * 更新区间:把区间[left,right]同时增加key
- * */
- void update_add(int id,int left,int right,int key){
- push_down(id);
- if(tree[id].left==left&&tree[id].right==right){//找到操作区间了
- tree[id].add=key;
- return;
- }
- int mid=Mid(tree[id].left,tree[id].right);
- if(right<=mid){
- update_add(id*2,left,right,key);
- }else if(left>mid){
- update_add(id*2+1,left,right,key);
- }else{
- update_add(id*2,left,mid,key);
- update_add(id*2+1,mid+1,right,key);
- }
- }
- /*
- * 查询区间
- * */
- int query(int id,int left,int right){
- push_down(id);
- if(tree[id].left==left&&tree[id].right==right){
- return tree[id].sum;
- }
- int mid=Mid(tree[id].left,tree[id].right);
- if(right<=mid){
- return query(id*2,left,right);
- }else if(left>mid){
- return query(id*2+1,left,right);
- }else{
- return query(id*2,left,mid)+query(id*2+1,mid+1,right);
- }
- }
- int main(){
- build(1,1,10);//利用a数组中的元素建树 ,0号元素不用
- int ans=query(1,1,5);//查询区间[1,5]元素的和
- printf("sum=%d\n",ans);
- update(1,6,10);
- //懒操作
- update_add(1,1,5,10);
- ans=query(1,1,6);
- printf("ans=%d\n",ans);
- 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
- #include<stdio.h>
- #include<algorithm>
- using namespace std;
- #define M 1000
- #define eps 1e-8
- struct Tree{
- int ll,rr,cover;//这里的cover之所以用int而不用bool是因为可能出现多条边重叠
- double lf,rf;
- double len;
- }tree[4*M];
- struct Line{
- double x,y1,y2;
- int flag;
- }line[M];
- double y[M];//用来离散化y坐标轴
- int cmp_double(const double &d1,const double &d2){
- double d=d1-d2;
- if(d>eps)//d1>d2
- return 1;
- else if(d<-eps)//d1<d2
- return -1;
- else //d1==d2
- return 0;
- }
- //比较函数
- bool cmp(const Line &l1,const Line &l2){//排序的比较函数
- return l1.x<l2.x;
- }
- //建树
- void build(int id,int ll,int rr){
- tree[id].ll=ll;tree[id].rr=rr;
- tree[id].lf=y[ll],tree[id].rf=y[rr];
- tree[id].len=tree[id].cover=0;
- if(ll+1==rr)return;//如果已经是元线段,则不用拆分了
- int mid=(ll+rr)>>1;
- build(id*2,ll,mid);
- build(id*2+1,mid,rr);
- }
- void lenght(int id){//求用于计算的线段实际长度
- if(tree[id].cover>0){
- tree[id].len=tree[id].rf-tree[id].lf;
- }else if(tree[id].ll+1==tree[id].rr){//元线段
- tree[id].len=0;
- }else
- tree[id].len=tree[id*2].len+tree[id*2+1].len;
- }
- //更新树
- void update(int id,Line line){
- if(cmp_double(tree[id].lf,line.y1)==0&&cmp_double(tree[id].rf,line.y2)==0){
- tree[id].cover+=line.flag;
- lenght(id);
- return;
- //这里的比较有点特别,值得关注一下,
- // 由于是浮点型的数据,所以不能直接用mid来比较
- }else if(cmp_double(line.y1,tree[2*id+1].lf)>=0){//用右孩子的左边界来比较
- update(id*2+1,line);
- }else if(cmp_double(line.y2,tree[id*2].rf)<=0){// 左孩子的右边界来比较
- update(id*2,line);
- }else{//分跨两个边界
- Line tmp;
- tmp=line;tmp.y2=tree[id*2].rf;
- update(2*id,tmp);
- tmp=line;tmp.y1=tree[id*2+1].lf;
- update(2*id+1,tmp);
- }
- lenght(id);//回溯的时候 修改,使根结点的len实时更新
- }
- int main(){
- int n;
- int i,t,ti=0;
- while(scanf("%d",&n)&&n){
- double x1,y1,x2,y2;
- for(t=i=1;i<=n;i++){
- scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
- line[t].x=x1;
- line[t].y1=y1;
- line[t].y2=y2;
- line[t].flag=1;
- y[t]=y1;
- t++;
- line[t].x=x2;
- line[t].y1=y1;
- line[t].y2=y2;
- line[t].flag=-1;
- y[t]=y2;
- t++;
- }
- sort(line+1,line+t,cmp);
- sort(y+1,y+t);//线段的离散化
- build(1,1,t-1);
- update ( 1, line[1] );//第一条边一定是入边
- double ans=0.00;
- for(i=2;i<t;i++){
- ans+=tree[1].len*(line[i].x-line[i-1].x);
- update(1,line[i]);//最后一条边肯定是出边,不用考虑
- }
- printf("Test case #%d\n",++ti);
- printf("Total explored area: %.2lf\n\n",ans);
- }
- return 0;
- }
此外,矩形分割也可以求解矩形面积相关的题!