线段树

线段树总结

    线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

    线段树的功能比以往的rmq、lca算法强大,不仅可以快速计算一个区间内的和,而且可以计算一个区间内最大(小)值,包含了rmq的功能和胜者树的功能,而且还可以做任意修改。

一、   线段树的构建。

用深搜构建线段树。

 

 

    如上图所示,就是一棵线段树的构建。线段树上面的数字,如1,10,代表1~10号区间,依此类推。

    深搜首先要确定停止条件,当左端点等于右端点如最后的叶子结点,就应该返回,同时赋值。

    然后继续深搜下去,先序遍历,先查left child。

    在查完以后,根结点的sum不要忘记加上左右结点的值。

    同样的,如果是求最大(小)值,就取max(min),其它相同。

int n,a[100005],s,t,q;
struct t
{
	long long sum;
}tree[400005];
void  buildtree(int root,int l,int r)
{
	if(l==r) 
	{
		tree[root].sum=a[l];
		printf("%d ",tree[root].sum);
		return;
	}
	buildtree(root*2,l,(l+r)/2);
	buildtree(root*2+1,(l+r)/2+1,r);
	tree[root].sum=tree[root*2].sum+tree[root*2+1].sum;
	printf("%d ",tree[root].sum);
}
int main()
{
	freopen("1660.in","r",stdin);
	freopen("1660.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	buildtree(1,1,n);
	return 0;
}

  

二.线段树查询区间。

    上面已经说了线段树的构建,可是如果要查询一个区间中的和或者最大(小)值要怎么样得到呢。

    假设要求最小值。首先,我们要判断当前的区间与要求的区间s,t是否相关,判断if(s>r||t<l) return oo;如果无关,就返回一个无穷大,答案就不会受到影响。 一开始的时候,我还是按照求和的写法,直接返回0,最小值肯定是0,所以就不对了。

    然后判断当前的区间是否被包含在要求的那个区间之内,如果包含,那么便直接返回那个区间的最小值就可以了。f(s<=l&&r<=t) return tree[root].mi;

为了方便下面求leftchild和rightchild,先开一个临时变量mid=(L+R)/2; 然后把leftchild和rightchild区间的最小值再min一下就可以得出最后的结果了。

同样的,最大值也是这样求。

int n,a[100005],s,t,q,an,ss,oo=1000000;
struct t
{
	int ma;
	int mi;
}tree[400005];
void  buildtree(int root,int l,int r)
{
	if(l==r) 
	{
		tree[root].ma=a[l];
		tree[root].mi=a[l];
		return;
	}
buildtree(root*2,l,(l+r)/2);
	buildtree(root*2+1,(l+r)/2+1,r);
	tree[root].ma=max(tree[root*2].ma,tree[root*2+1].ma);
	tree[root].mi=min(tree[root*2].mi,tree[root*2+1].mi);
}
int get1(int root,int l,int r,int s,int t)
{
	int ans1=0,ans2=0 ,ans=0;
	if(s<=l&&r<=t) return tree[root].ma;
	if(s>r||t<l) return -oo;
	int mid=(l+r)/2;
	ans1=get1(root*2,l,mid,s,t);
	 ans2=get1(root*2+1,mid+1,r,s,t);
	ans=max(ans1,ans2);
	return  ans;
}
int get(int root,int l,int r,int s,int t)
{
	int ans1=0,ans2=0,ans=0;
	if(s<=l&&r<=t) return tree[root].mi;
	if(s>r||t<l) return oo;
	int mid=(l+r)/2;
 	ans1=get(root*2,l,mid,s,t);
	ans2=get(root*2+1,mid+1,r,s,t);
	ans=min(ans1,ans2);
	return  ans;
}
int main()
{
	freopen("1656.in","r",stdin);
	freopen("1656.out","w",stdout);
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	buildtree(1,1,n);
	while(q)
	{
		q--;
		scanf("%d%d",&s,&t);
		if(s>t) swap(s,t);
		an=get1(1,1,n,s,t);
		ss=get(1,1,n,s,t);
		int ans=an-ss;
		printf("%d",ans);

	}
	return 0;
}

  

三、   修改一个数字

        当L==x并且R==x时把tree[root].ma修改成y。然后再改变两个孩子,但是改变完以后,要记得重新max一下tree[root]的值,保证正确性。

void change(int root,int l,int r,int x,int y)//改动
{
	if(l>x||x>r) return;
	if(l==x&&r==x) 
	{
		tree[root].ma=y;
		return;
	}
	int mid=(l+r)/2;
	change(root*2,l,mid,x,y);
	change(root*2+1,mid+1,r,x,y);
	tree[root].ma=max(tree[root*2].ma,tree[root*2+1].ma);
}

  

三、   线段树修改一段的值

    这个相对起前面的就复杂很多了。首先,如果要改一段,一定会影响到很多。但如果逐个去改就好比for循环,要用很长的时间。

    我们可以把要加减的值先记录下来,把tree[root].sum加(减)上,不作其他处理。如果当前的区间完全包含在给出的区间里面时,便把当前这个root的记录增加值add加上,然后return,这个和前面是一样的,不相干时也return。

    如果不符合上面的条件,就把根结点的add分散给孩子,首先,把左右孩子的add+=根结点的add,然后,左右孩子的sum要由长度(R-L+1)乘add。最后,再把根结点的add清零。

    然后,同样是轮到左孩子和右孩子。其他基本不变。

    但是,如果是求最大(小)要注意了,不能sum=(R-L+1)*add这样求的是sum值,直接加上add就可以了。

  取sum:

struct t
{
	long long add;
	long long sum;
}tree[400005];
void buildtree(int root,int l,int r)//建树
{
	if(l==r) 
	{
		tree[root].sum=a[l];
		return;
	}
	int mid=(l+r)/2;
	buildtree(root*2,l,mid);
	buildtree(root*2+1,mid+1,r);
	tree[root].sum=tree[root*2].sum+tree[root*2+1].sum;
}
void down(int root,int l,int r)//分散
{
	tree[root*2].add+=tree[root].add;
	tree[root*2+1].add+=tree[root].add;
	int mid=(l+r)/2;
	tree[root*2].sum+=(mid-l+1)*tree[root].add;
	tree[root*2+1].sum+=(r-mid)*tree[root].add;
	tree[root].add=0;
}
void update(int root,int l,int r,int s,int t,long long v)//改动
{
	if(s>r||t<l) return;
	if(s<=l&&t>=r)
	{
		tree[root].sum+=(r-l+1)*v;
		return;
	}
	down(root,l,r);
	int mid=(l+r)/2;
	update(root*2,l,mid,s,t,v);
	update(root*2+1,mid+1,r,s,t,v);
	tree[root].sum=tree[root*2].sum+tree[root*2+1].sum;
}
long long get(int root,int l,int r,int s,int t)//得出答案
{
	long long ans=0;
	if(s>r||t<l) return 0;
	if(s<=l&&t>=r)
	{
		return tree[root].sum;
	}
	down(root,l,r);
	int mid=(l+r)/2;
	ans+=get(root*2,l,mid,s,t);
	ans+=get(root*2+1,mid+1,r,s,t);
	return ans;
}

  取max:

struct t
{
	int add;
	int sum;
}tree[400005];
void buildtree(int root,int l,int r)
{
	if(l==r) 
	{
		tree[root].sum=a[l];
		return;
	}
	int mid=(l+r)/2;
	buildtree(root*2,l,mid);
	buildtree(root*2+1,mid+1,r);
	tree[root].sum=max(tree[root*2].sum,tree[root*2+1].sum);
}
void down(int root,int l,int r)
{
	tree[root*2].add+=tree[root].add;
	tree[root*2+1].add+=tree[root].add;
	int mid=(l+r)/2;
	tree[root*2].sum+=tree[root].add;
	tree[root*2+1].sum+=tree[root].add;
	tree[root].add=0;
}
void update(int root,int l,int r,int s,int t,int v)
{
	if(s>r||t<l) return;
	if(s<=l&&t>=r)
	{
		tree[root].add+=v;
		tree[root].sum+=v;
		return;
	}
	down(root,l,r);
	int mid=(l+r)/2;
	update(root*2,l,mid,s,t,v);
	update(root*2+1,mid+1,r,s,t,v);
	tree[root].sum=max(tree[root*2].sum,tree[root*2+1].sum);
}
int get(int root,int l,int r,int s,int t)
{
	int ans=-oo;
	if(s>r||t<l) return -oo;
	if(s<=l&&t>=r)
	{
		return tree[root].sum;
	}
	down(root,l,r);
	int mid=(l+r)/2;
	ans=max(get(root*2+1,mid+1,r,s,t),get(root*2,l,mid,s,t));
	
	return ans;
}

  

posted @ 2017-08-19 15:17  yiyiyizqy  阅读(162)  评论(0编辑  收藏  举报