zoukankan      html  css  js  c++  java
  • c语言数据结构之线段树详解;例题:校门外的树(poj2808或者vijos1448)

    线段树:它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个结点。 也就是说线段树的每一个结点对应一个区间,其中根节点对应区间[1,n] 对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。 最后的叶子结点数目为N,即整个线段区间的长度。 基于二叉树编号的优秀性质,我们使用一维数组来实现线段树。

    线段树支持以下操作 1、修改单点或者区间 2、查询单点或者区间 其实使用树状数组解决的问题使用线段树都可以解决,但是反过来却不行。 复杂度:假设区间长度为N,那么所有操作的复杂度都是O(logN)级别的。

    一维数组模拟实现:

    int id[4*N],sum[4*N],lson[4*N],rson[4*N]; 

    一维数组以完全二叉树方式存储线段树的编程复杂度小,执行效率较高,但浪费空间。像线段树这样区间长度并不一定是 2n 的二叉树,其占用空间为 2的(最深结点的深度)次幂,就给线段树的空间占用造成了很大的不确定性。 通过证明和实践得出,使用一维数组模拟实现时一定要开到4*N!!!切记一定开四倍!!不然会死的很惨。。。

    例如sum(求和),max(求最大值),min(求最小值)之类的信息,维护了一个结点的属性,具有一定的递推性质,可以在维护的时候自下而上的递推计算。 例如,把区间统一加减delta,lazy标记等信息,不仅仅代表当前结点(区间)的属性,还表示了整个子树(子区间)的属性,可以在需要进一步处理时将这种性质自上而下的传递。 线段树优秀的性质:假设修改区间【x,y】,遇到某个结点维护的区间是区间【a,b】,并且【a,b】是【x,y】的子区间,那么就在当前结点修改整个区间【a,b】的属性,不用修改到叶子结点,否则时间复杂度还不如O(n)修改。

    因为线段树的性质,我们可以把结点需要维护的属性存到不同的数组中,当然,也可以每一个结点开一个结构体,把区间端点以及需要维护的性质都存到结构体中。但是为了节省存储空间,一般我们把区间端点用参数传递下去,而不用存储。

    例题:

    校门外的树(区间修改,区间查询)

    某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。 我们可以把马路看成一个数轴,马路的一端在数轴1的位置,另一端在L的位置; 数轴上的每个整数点,即1,2,...L的位置,都种有一棵树。 由于马路上的N个区域[L1,R1],[L2,R2]...[LN,RN]要用来建地铁,区域之间可能有重合的部分。 现在要把这些区域的树(包括区域端点处的两棵树)移走。 你的任务是计算每次移走这些树后,马路上还有多少棵树。 对于100%的数据, N,L,Li,Ri <= 105。

    线段树每个结点维护的属性就是:此结点所对应的区间中树的数量。 显然叶子结点初值为1,根节点初值为n。 区间修改:[L,R]区间修改。 区间查询(对应的线段树中根节点的查询):对于多次查询来说,其实就是查询根节点所维护的属性。

    为了不麻烦,我们这样做

    #define lson pos<<1
    #define rson pos<<1|1
    using namespace std;
    int n,m,a[M<<2];

     注:<<2的意思是乘4,<<1是乘2,<<1|1是乘2加一。

    递归建树:

    void build(int pos,int l,int r)
    {
        int mid=l+r>>1;
        a[pos]=r-l+1;
        if(l==r) return ;
        build(lson,l,mid);
        build(rson,mid+1,r);
    }
    

    递归修改:

    void update(int pos,int l,int r,int x,int y)
    {
        int mid=l+r>>1;
        if(a[pos]==0) return;
        if(x<=l&&r<=y)
    	{
    		a[pos]=0;
    		return;
    	}
        if(y<=mid)update(lson,l,mid,x,y);
        else
    	{
    		if(x>mid)
    		{
    			update(rson,mid+1,r,x,y);
    		}
    		else
    	    {
    	        update(lson,l,mid,x,y);
    	        update(rson,mid+1,r,x,y);
    	    }
    	}
        a[pos]=a[lson]+a[rson];//求和是这样,求最大值就是求a[lson]和a[rson]的最大值,最小值一样
    }

     废话不多说,源代码奉上。

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    #define M 100010
    #define lson pos<<1
    #define rson pos<<1|1
    using namespace std;
    int n,m,a[M<<2];
    void build(int pos,int l,int r)
    {
        int mid=l+r>>1;
        a[pos]=r-l+1;
        if(l==r) return ;
        build(lson,l,mid);
        build(rson,mid+1,r);
    }
    void update(int pos,int l,int r,int x,int y)
    {
        int mid=l+r>>1;
        if(a[pos]==0) return;
        if(x<=l&&r<=y)
    	{
    		a[pos]=0;
    		return;
    	}
        if(y<=mid)update(lson,l,mid,x,y);
        else
    	{
    		if(x>mid)
    		{
    			update(rson,mid+1,r,x,y);
    		}
    		else
    	    {
    	        update(lson,l,mid,x,y);
    	        update(rson,mid+1,r,x,y);
    	    }
    	}
        a[pos]=a[lson]+a[rson];
    }
    int main()
    {
    	scanf("%d%d",&n,&m);
    	build(1,1,m);
    	for(int i=1;i<=n;i++)
    	{
    		int xd,yd;
    		scanf("%d%d",&xd,&yd);
    		update(1,1,m,xd,yd);
    		printf("%d
    ",a[1]);
    	}
    	return 0;
    }

    最后说一句,切记!数组空间开四倍!

  • 相关阅读:
    《构建之法(第三版)》第一章学习总结
    20189220余超 2019年密码与安全新技术讲座-课程总结报告
    学号20189220 2018-2019-2 《密码与安全新技术专题》第六周作业
    学号20189220余超 2018-2019-2 《密码与安全新技术专题》第七周作业
    20189200余超 2018-2019-2 移动平台应用开发实践作项目代码分析
    2018-2019-2学号20189220余超《移动平台应用程序开发实践》课程总结
    20189200余超 2018-2019-2 移动平台应用开发实践第十二周作业
    20189220余超 团队博客——阅读软件app
    20189200余超 2018-2019-2 移动平台应用开发实践第十一周作业
    20189200余超 2018-2019-2 移动平台应用开发实践第十周作业
  • 原文地址:https://www.cnblogs.com/lcccAngelo/p/9757229.html
Copyright © 2011-2022 走看看