线段树1

先讲解一下线段树

线段树用于区间操作的优化,来看一道这样的题:

https://www.luogu.org/problem/P2068

在一个序列中,支持以下操作:修改序列中一个数,查询其中y一段区间的和

元素数<=100000,操作数<=10000.

由于数据较大,n*m显然过不了,这时,就要用到线段树了:

这就是线段树示意图

每个节点上都有一个区间,这个节点的点权就是这个区间所有数的和

但不用担心初始化的时间,因为每个节点的点权等于它的左右儿子点权之和

一个节点的左右儿子的点权分别对应l~(l+r)/2的和与(l+r)/2+1~r的和

到叶节点时这个点权就是对应这个数。

所以build就可以这样写

void build(int l,int r,int root)
{
	if(l==r) 
	{
		num[root]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(l,mid,root*2);
	build(mid+1,r,root*2+1);
	num[root]=min(num[root*2],num[root*2+1]);
}

比如,我要查询的是3~7这个区间,那它的和就是

 

这三个节点的点权之和

现在我们看一下查询的时间复杂度,很明显大约是O(logn)

现在,来看一下查询的代码

int search(int p,int q,int l,int r,int root)
{
	if(p==l && r==q)
	{
		return num[root];
	}
	int mid=(l+r)/2;
	if(p>=mid+1) return search(p,q,mid+1,r,root*2+1);
	else if(q<=mid) return search(p,q,l,mid,root*2);
	else return search(p,mid,l,mid,root*2)+search(mid+1,q,mid+1,r,root*2+1);
}

代码解释

如果要查询的区间被该节点的左子树或右子树完全包含,就只用查找一个子树就行了

否则要查询的区间一定是跨越了两个子树,这样就要查询两次

如果要查询的区间与该节点对应的区间刚好重合,这样就直接return这个值就好了

 

最后是修改,例如我要修改6这个节点,那要修改的就是

这五个节点,时间复杂度也是O(logn)(因为每层一个)

简单看一下代码

void change(int p,int val,int l,int r,int root)
{
	if(l==r)
	{
		num[root]+=val;
		return;
	}
	int mid=(l+r)/2;
	if(p>=mid+1) change(p,val,mid+1,r,root*2+1);
	else change(p,val,l,mid,root*2);
	num[root]=num[root*2]+num[root*2+1];
}

代码解释

如果要查询的节点再该节点的左子树,就查找一个左子树就行了

否则就查找一个右子树就好了

如果要查询的节点与该节点刚好重合,这样就直接修改这个值就好了

 

最后是这道题的完整代码

#include<bits/stdc++.h>
using namespace std;
int num[100000*4];
void build(int l,int r,int root)
{
	if(l==r)
	{
		num[root]=0;
		return;
	}
	int mid=(l+r)/2;
	build(l,mid,root*2);
	build(mid+1,r,root*2+1);
	num[root]=num[root*2]+num[root*2+1];
}
void change(int p,int val,int l,int r,int root)
{
	if(l==r)
	{
		num[root]+=val;
		return;
	}
	int mid=(l+r)/2;
	if(p>=mid+1) change(p,val,mid+1,r,root*2+1);
	else change(p,val,l,mid,root*2);
	num[root]=num[root*2]+num[root*2+1];
}
int search(int p,int q,int l,int r,int root)
{
	//printf("%d %d %d %d %d\n",p,q,l,r,root);
	if(p==l && r==q)
	{
		return num[root];
	}
	int mid=(l+r)/2;
	if(p>=mid+1) return search(p,q,mid+1,r,root*2+1);
	else if(q<=mid) return search(p,q,l,mid,root*2);
	else return search(p,mid,l,mid,root*2)+search(mid+1,q,mid+1,r,root*2+1);
}
int main()
{
	int n,w;
	scanf("%d%d",&n,&w);
	for(int i=1;i<=w;i++)
	{
		char t;
		int x,y;
		cin>>t>>x>>y;
		if(t=='x')
		{
			change(x,y,1,n,1);
		}
		else if(t=='y')
		{
			printf("%d\n",search(x,y,1,n,1));
		}
	}
	return 0;
}

最后再给大家推荐几道线段树的养生的模板

https://www.luogu.org/problem/U85066

https://www.luogu.org/problem/P1637

 

posted @ 2019-08-31 22:06  CZD648  阅读(189)  评论(0编辑  收藏  举报
Live2D