「学习笔记」普通线段树

Segment Tree

十分朴素的树。


线段树是一种及其常用的数据结构。
它面对各种类型的数据都有应付的方式。
而且不断有各种 「线段树·改」,使得其功能更为强大。

我对此深有体会。
以前我总喜欢用树状数组代替线段树,对于线段树这个数据结构我是抗拒的。反正都能用干嘛要用麻烦的。
但是线段树的功能之完善程度不是一般强,而是强到离谱。堪称学了之后收益最大的数据结构。


维护数据?
作为一钟数据结构,其目的自然是维护某种类型的数据。
一般线段树的每个结点都表示了一个区间。
在这里插入图片描述

线段树是基于分治思想的。
观察一下,这显然是一棵完全二叉树。
那么建树的过程也就特别显而易见了。

建树的过程大致如下:

  1. 向下递归;
  2. 如果当前结点是叶子结点,存储数据;
  3. 由子结点的值更新当前结点的值。
//更新
void push_up(int now)  {
	int L_son=now*2, R_son=now*2+1;
	tree[now].data=tree[L_son].data+tree[R_son].data;
}

//建树
void build(int now, int L, int R) {
	tree[now].L=L; tree[now].R=R; tree[now].len=R-L+1;
	
	if (L==R) {
		tree[now].data=a[L];
		
		return ;
	}
	
	int mid=(L+R)>>1;
	int L_son=now*2, R_son=now*2+1;
	build(L_son, L, mid); build(R_son, mid+1, R);
	
	push_up(now);
}

单点修改&单点查询很简单,这里就不赘述了。


区间查询和建树差不多,只是没有遍历整棵树。因此进行区间查询操作时需要判断往哪棵子树走。

//区间查询
int query(int now, int L, int R) {
	if (L<=tree[now].L && R>=tree[now].R) {
		return tree[now].data;
	}
	
	push_down(now);
	
	int mid=(tree[now].L+tree[now].R)>>1, res=0;
	int L_son=now*2, R_son=now*2+1;
	if (L<=mid) {
		res+=query(L_son, L, R);
	}
	if (R>mid) {
		res+=query(R_son, L, R);
	}
	
	return res;
}


区间修改…
一个个位置单点修改?显然不可能,这样的复杂度是无法接受的。
这里就需要引入懒标记(延迟标记)的概念了。其实对于它的操作早就出现了(就是上面的 push_down,意为下传懒标记(push down lazy tag))
对于需要更新的值,我们打上标记,等需要时再进行修改,如此效率就得到了保证。
事实上区间修改实现起来和区间查询很像。(事实上每个操作都差不多,毕竟都是沿着树向下走)

//下传懒标记
void push_down(int now) {
	int lazy_tag=tree[now].tag;
	tree[now].tag=0;
	
	int L_son=now*2, R_son=now*2+1;
	if (lazy_tag) {
		tree[L_son].data+=lazy_tag*(tree[L_son].R-tree[L_son].L+1);
		tree[R_son].data+=lazy_tag*(tree[now].len);
		
		tree[L_son].tag+=lazy_tag;
		tree[R_son].tag+=lazy_tag;
	}
}

//区间修改
void update(int now, int L, int R, int val) {
	if (L<=tree[now].L && R>=tree[now].R) {
		tree[now].data+=val*(tree[now].R-tree[now].L+1);
		tree[now].tag+=val;
		
		return ;
	}
	
	push_down(now);
	
	int mid=(tree[now].L+tree[now].R)>>1;
	int L_son=now*2, R_son=now*2+1;
	if (L<=mid) {
		update(L_son, L, R, val);
	}
	if (R>mid) {
		update(R_son, L, R, val);
	}
	
	push_up(now);
}

c o d e : code: code:

#include <stdio.h>
#include <ctype.h>
#pragma GCC diagnostic error "-std=gnu++11"
#define reg register
using namespace std;

namespace fast_IO {
	template <typename conv>
	inline conv read(conv &x) {
		char ch=getchar(); int flag=1; x=0;
		while (!isdigit(ch)) {if (ch=='-') flag=-flag; ch=getchar();}
		while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-48; ch=getchar();}
		x*=flag;
	}
	template <typename conv, typename ... Args>
	inline conv read(conv &first, Args & ... args) {
		read(first);
		read(args...);
	}
	
	template <typename conv>
	inline conv write(conv x) {
		if (x<0) {putchar('-'); x=-x;}
		if (x>9) {write(x/10);}
		putchar(x%10+48);
	}
	template <typename conv, typename ... Args>
	inline conv write(conv first, Args ... args) {
		write(first);
		write(args...);
	}
}
using namespace fast_IO;

const int maxn=1e6+5;
int n, m;
int a[maxn];
struct segment_tree {
	int L, R, len;
	int data;
	int tag; /*addition*/
} tree[maxn<<2];

//更新
void push_up(int now)  {
	int L_son=now*2, R_son=now*2+1;
	tree[now].data=tree[L_son].data+tree[R_son].data;
}

//建树 
void build(int now, int L, int R) {
	tree[now].L=L; tree[now].R=R; tree[now].len=R-L+1;
	
	if (L==R) {
		tree[now].data=a[L];
		
		return ;
	}
	
	int mid=(L+R)>>1;
	int L_son=now*2, R_son=now*2+1;
	build(L_son, L, mid); build(R_son, mid+1, R);
	
	push_up(now);
}

//下传懒标记
void push_down(int now) {
	int lazy_tag=tree[now].tag;
	tree[now].tag=0;
	
	int L_son=now*2, R_son=now*2+1;
	if (lazy_tag) {
		tree[L_son].data+=lazy_tag*(tree[L_son].R-tree[L_son].L+1);
		tree[R_son].data+=lazy_tag*(tree[now].len);
		
		tree[L_son].tag+=lazy_tag;
		tree[R_son].tag+=lazy_tag;
	}
}

//单点查询 
int query(int now, int pos) {
	if (tree[now].L==tree[now].R) {
		return tree[now].data;
	}
	
	push_down(now);
	
	int mid=(tree[now].L+tree[now].R)>>1;
	int L_son=now*2, R_son=now*2+1;
	if (pos<=mid) {
		return query(L_son, pos);
	}
	else {
		return query(R_son, pos);
	}
}

//区间查询 
int query(int now, int L, int R) {
	if (L<=tree[now].L && R>=tree[now].R) {
		return tree[now].data;
	}
	
	push_down(now);
	
	int mid=(tree[now].L+tree[now].R)>>1, res=0;
	int L_son=now*2, R_son=now*2+1;
	if (L<=mid) {
		res+=query(L_son, L, R);
	}
	if (R>mid) {
		res+=query(R_son, L, R);
	}
	
	return res;
}

//单点修改
void update(int now, int pos, int val) {
	if (tree[now].L==tree[now].R) {
		tree[now].data+=val;
		return ;
	}
	
	int mid=(tree[now].L+tree[now].R)>>1;
	int L_son=now*2, R_son=now*2+1;
	if (pos<=mid) {
		update(L_son, pos, val);
	}
	else {
		update(R_son, pos, val);
	}
	
	push_up(now);
}

//区间修改 
void update(int now, int L, int R, int val) {
	if (L<=tree[now].L && R>=tree[now].R) {
		tree[now].data+=val*(tree[now].R-tree[now].L+1);
		tree[now].tag+=val;
		
		return ;
	}
	
	push_down(now);
	
	int mid=(tree[now].L+tree[now].R)>>1;
	int L_son=now*2, R_son=now*2+1;
	if (L<=mid) {
		update(L_son, L, R, val);
	}
	if (R>mid) {
		update(R_son, L, R, val);
	}
	
	push_up(now);
}

int main() {
	read(n, m);
	for (reg int i=1; i<=n; i++) {
		read(a[i]);
	}
	build(1, 1, n);
	
	for (reg int i=1; i<=m; i++) {
		int op, x, y, k;
		read(op);
		if (op==1) {
			read(x, y, k);
			update(1, x, y, k);
		}
		else {
			read(x, y);
			write(query(1, x, y)); putchar('\n');
		}
	}
	
	return 0;
}

普通线段树就此落幕。
但是线段树绝不是仅此而已。


线段树需要开的4倍的空间,这在很多时候是无法接受的。
这就需要用到 线段树进阶——动态开点 啦。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值