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\n",a[1]);
	}
	return 0;
}

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

posted @ 2018-09-12 09:39  暗燚  阅读(789)  评论(0编辑  收藏  举报