线段树详解

线段树及其应用

线段树的几个基础操作:建树,单点查询,单点修改,区间查询,区间修改。其代码的主要思想为二分。参考博客:https://blog.csdn.net/qq_39826163/article/details/81436440

数据结构:

struct node
{
    int l;                  //左端点
    int r;                  //右端点
    int sum;              	//区间和,因题目而异
    int f;            //懒标记
}tree[4*maxn+1];

1.建树

建树的过程分为三步:1:给定左右端点的确定范围;2:如果是叶子结点,储存需要维护的信息;3:状态合并。下面是实现代码:

void build(int l,int r,int cur)
{
	tree[cur].l = l,tree[cur].r = r;
	if(tree[cur].l == tree[cur].r){
		tree[cur].sum = arr[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l, mid, cur << 1);
	build(mid + 1, r, cur << 1 | 1);
	pushup(cur);					//状态合并
}

2.单点查询

单点查询与二分查询法基本一致,如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。如果不是,因为这是二分法,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid,则如果x<=mid,则递归它的左孩子,否则递归它的右孩子。下面是实现代码:

int ask(int pos,int cur)            //cur为当前结点,x为待查位置
{
    if(tree[cur].l==tree[cur].r)		return tree[k].value;
    int mid=(tree[cur].l+tree[cur].r)>>1;
    if(pos<=mid) ask(pos,cur<<1);       
    else ask(pos,cur<<1|1);				//递归左右孩子
}

3.单点修改

和单点查询原理类似,结合建树过程,我们以增加某一个区间的长度代码为例,下面是实现代码:

void modify(int pos,int x,int cur)				//在pos位置修改x(增加x),cur为当前结点编号
{
	if(tree[cur].l == tree[cur].r){
		tree[cur].sum += x;
		return;
	}
    if(tree[cur].f)	pushdown(cur);
	int mid = (tree[cur].l + tree[cur].r) >> 1;
	if(pos <= mid)	modify(pos,x,cur<<1);
	else         	modify(pos,x,cur<<1|1);
	pushup(cur);
}

4.区间查询

区间查询分为三种状态,1、当前结点区间的值全部为答案的一部分,;2、当前结点区间只有一部分是答案,3、当前结点区间包含了待查询的区间,根据x,y与mid的情况往下走。

即mid=(l+r)/2

y<=mid ,即 查询区间全在,当前区间的左子区间,往左孩子走;x>mid 即 查询区间全在,当前区间的右子区间,往右孩子走否则,两个子区间都走。

下面是实现代码:

void query(int l,int r,int cur)			  //l,r为待查询区间
{
	if(l <= tree[cur].l && tree[cur].r <=r ){
		ans += tree[cur].sum;
		return;
	}
    if(tree[cur].f)	pushdown(cur);		//将更新信息传递给左右子树
	int mid = (tree[cur].l + tree[cur].r) >> 1;
	if(l <= mid) query(l,r,cur<<1);
	if(mid < r)  query(l,r,cur<<1|1);
}

5.区间修改

如果要修改一个区间的值,给一个区间内的每个数都加或减或修改时,如果我们只想查询某一个子区间的值,如修改[1,100]而只查询[1,2]的值,如果给所有区间都改得画,在树的深度很高的情况下会很浪费。所以这里引入了一个新状态——懒标记,其作用是存储到这个节点的修改信息,暂时不把修改信息传到子节点。就像家长扣零花钱,你用的时候才给你,不用不给你。下面是懒标记的具体实现过程:

懒标记下移:

void pushdown(int cur)
{
    tree[cur<<1].f+=tree[cur].f;
    tree[cur<<1|1].f+=tree[cur].f;
    tree[cur<<1].sum+=tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1);
    tree[cur<<1|1].sum+=tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1);
    tree[cur].f=0;
}

还有上面提到的pushup函数,我认为它和oushdown函数一起,是线段树的核心,其他的不过是模板而已,而这个是线段树真正灵活多变的地方,对于任意给定一个题目,你要依据题意,题目需要维护什么,你就维护什么,比如上面一直再说的oushup函数和这里的Pushdown,在这起到的是维护一个区间和的作用。不同题目真正不一样的代码应该就是这两个了。

//以维护区间和为例:
inline void pushup(int cur)
{
	tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum;
}

区间修改代码:

void modify_interval(int l,int r,int x,int cur)     //[a,b]为待修改的区间,x为区间修改的值
{
    if(tree[cur].l>=l&&tree[cur].r<=r)      //当前区间全部对要修改的区间有用 
    {
        tree[cur].value+=(tree[cur].r-tree[cur].l+1)*x;       //(r-1+1)区间点的总数
        tree[cur].f+=x;
        return;
    }
    if(tree[cur].f) 	pushdown(cur);                //懒标记下移
    int mid=(tree[cur].l+tree[cur].r)>>1;
    if(l<=mid) 		modify_interval(l,r,x,cur<<1);
    if(r>mid) 		modify_interval(l,r,x,cur<<1|1);
    pushup(cur);
}

例题AC代码:poj-2528:题意,每次在[l,r]区间贴广告,最多能看见多少个广告牌?

思路:离散化+线段树区间染色

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>

using namespace std;
const int maxn=1e5+50;
struct node
{
	int l,r,num;
}tree[maxn<<2];
int n,T,cnt,tot,li[maxn],ri[maxn];
int point[maxn<<1];
int ans;
bool vis[maxn];
inline void pushdown(int cur)
{
	tree[cur<<1].num=tree[cur].num;
	tree[cur<<1|1].num=tree[cur].num;
	tree[cur].num=0;
}
inline void build(int l,int r,int cur)		//initialization
{
	tree[cur].l=l,tree[cur].r=r;
	if(l==r){
		tree[cur].num=0;
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,cur<<1);
	build(mid+1,r,cur<<1|1);
	tree[cur].num=0;
}
inline void modify(int l,int r,int x,int cur)
{
	if(tree[cur].l>=l&&tree[cur].r<=r){
		tree[cur].num=x;
		return;
	}
	if(tree[cur].num)	pushdown(cur);
	int mid=(tree[cur].l+tree[cur].r)>>1;
	if(l<=mid)	modify(l,r,x,cur<<1);
	if(mid<r)	modify(l,r,x,cur<<1|1);
}
inline void query(int l,int r,int cur)
{
	if(tree[cur].num&&!vis[tree[cur].num]){
		vis[tree[cur].num]=1;
		ans++;
		return;
	}
	if(l==r)	return;
	if(tree[cur].num)	pushdown(cur);
	int mid=(l+r)>>1;
	query(l,mid,cur<<1);
	query(mid+1,r,cur<<1|1);
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		memset(vis,false,sizeof(vis));
		scanf("%d",&n);
		cnt=ans=0;
		for(int i=1;i<=n;++i)	scanf("%d %d",&li[i],&ri[i]);
		for(int i=1;i<=n;++i){
			point[++cnt]=li[i],	point[++cnt]=ri[i];
		}
		sort(point+1,point+cnt+1);
		int now=unique(point+1,point+cnt+1)-(point+1);
		tot=now;
		for(int i=2;i<=now;++i){
			if(point[i]-point[i-1]>1)
				point[++tot]=point[i-1]+1;
		}
		sort(point+1,point+tot+1);
		build(1,tot,1);
		for(int i=1;i<=n;++i){
			int l=lower_bound(point+1,point+tot+1,li[i])-point;
			int r=lower_bound(point+1,point+tot+1,ri[i])-point;		//O(n*logn) algorithm
			modify(l,r,i,1);
		}
		query(1,tot,1);
		printf("%d\n",ans);
	}
	system("pause");
}

POJ-3468 题意:裸线段树区间修改+区间查询,注意区间查询的时候别忘了pushdown就好了。

#include<cstdio>
#include<cstdlib>
#include<iostream>

using namespace std;
const int maxn=1e5+50;
typedef long long LL;
int arr[maxn],n,q;
LL ans;
struct node
{
	int l,r,f;
	LL sum;
}tree[maxn<<2];
inline void pushup(int cur)
{
	tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum;
}
inline void build(int l,int r,int cur)
{
	tree[cur].l=l,tree[cur].r=r;
	tree[cur].sum=tree[cur].f=0;
	if(tree[cur].l==tree[cur].r){
		tree[cur].sum=1LL*arr[l];
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,cur<<1);
	build(mid+1,r,cur<<1|1);
	pushup(cur);
}
inline void pushdown(int cur)
{
	tree[cur<<1].f+=tree[cur].f;
	tree[cur<<1|1].f+=tree[cur].f;
	tree[cur<<1].sum+=1LL*tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1);
	tree[cur<<1|1].sum+=1LL*tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1);
	tree[cur].f=0;
}
inline void query(int l,int r,int cur)
{
	if(l<=tree[cur].l&&tree[cur].r<=r){
		ans+=tree[cur].sum;
		return;
	}
	if(tree[cur].f)	pushdown(cur);
	int mid=(tree[cur].l+tree[cur].r)>>1;
	if(mid>=l)	query(l,r,cur<<1);
	if(mid<r)	query(l,r,cur<<1|1);
}
inline void modify(int l,int r,int x,int cur)
{
	if(l<=tree[cur].l&&tree[cur].r<=r){
		tree[cur].f+=x;
		tree[cur].sum+=1LL*x*(tree[cur].r-tree[cur].l+1);
		return;
	}
	if(tree[cur].f)	pushdown(cur);
	int mid=(tree[cur].l+tree[cur].r)>>1;
	if(mid>=l)	modify(l,r,x,cur<<1);
	if(mid<r)	modify(l,r,x,cur<<1|1);
	pushup(cur);
}

int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i)	scanf("%d",&arr[i]);
	build(1,n,1);
	while(q--)
	{
		char t;
		getchar();
		scanf("%c",&t);
		if(t=='Q'){
			ans=0;
			int a,b;
			scanf("%d %d",&a,&b);
			query(a,b,1);
			printf("%lld\n",ans);
		}
		else{
			int a,b,c;
			scanf("%d %d %d",&a,&b,&c);
			modify(a,b,c,1);
		}
	}
	system("pause");
}

ps:pushdown可以写在函数最上方,玄学写法我也不懂,先pushdown,wa了可以试试qwq

posted @ 2020-02-02 22:19  StungYep  阅读(358)  评论(0编辑  收藏  举报