2021-07-13 线段树 专题

线段树 专题

一.关于线段树

0.说在前面

​ 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。

1.例题

【模板】线段树 1

2.认识线段树

​ 线段树,顾名思义,就是将一个大的区间划分成一个一个小的区间,直到剩余自己为止,然后形成一棵树,如下图,这是一棵1到10的线段树:

普通线段树

3.建立线段树

有一个一维数组 a[6] = { 0 , 1 , 2, 3 , 4 , 5 , 6 },把他建立成一棵线段树可以这样做:

设线段树的根节点为1,用数组 tree 来储存我们的线段树, tree[i] 表示在 i 节点上的值(区间最小值 or 区间最大值 or 区间总和),这是一棵最大值的线段树。

线段树

黑色圆框里的是区间,tree[i] 是节点,红色的数是 tree[i] 的值。

不难发现 tree[1] = max ( a[1] , a[2] , a[3] , a[4] , a[5] , a[6] )。

我们通过观察,可以发现当前节点 i 的左节点为 2i ,右节点为 2i+1 。我们继续观察,有没有发现如果 tree[i] 表示的区间大小等于 1 的话(区间大小指的是区间包含的元素的个数,即 a 的个数。设 tree[j] 表示区间 [s,t],它的区间大小就是 t-s+1),那么 tree[i] 所表示的区间 [s,t] 中肯定有 s=t,且 tree[i] = a[s] = a[t] 。这就是线段树的递归边界。

让我们一起看一下如何建树的程序吧,code:

void build(int i,int x,int y)
{
	if(x==y)
	{
		tree[i]=a[x];
		return;
	}
	int m=(x+y)/2;
	build(i+i,x,m);
	build(i+i+1,m+1,y);
	tree[i]=max(tree[i+i],tree[i+i+1]);
}

4.查询线段树

查询线段树的 [l,r] 的区间的最大值 or 最小值 or 总和 , 我们假设要枚举 [1,6] ,就直接选择 tree[1] 的值,因为 tree[1] 的区间就是为 [1,6],如图:

区间1~6

如果我们要查询 [2,5] 区间内的最大值,可以先分成 [2,3] [4,5] ,最后在把这两个区间分成更小的区间 [2,2] [3,3] [4,4] [5,5] ,如下图:

区间2~5

分好区间后,可以先查询 [2,2] [3,3] [4,4] [5,5] 分别的值,查到后返回到 [2,3] [4,5] 中,最后就查询到了 [2,5] 区间的最大值了。不懂的可以琢磨一下代码:

void qeury(int i,int x,int y)
{
	if(y<pl||x>pr)
		return;
	if(x>=pl&&y<=pr)
	{
		px=max(px,tree[i]);
		return;
	}
	int m=(x+y)/2;
	down(i);
	qeury(i+i,x,m);
	qeury(i+i+1,m+1,y);
}

5.线段树区间修改

(1)朴素算法

像查询线段树差不多,找到了就修改,现在要重点将 懒惰标志

(2)懒惰标志

我们先看个故事:

一个父亲,他的名字叫 father ,他有两个儿子,分别为 the first son,the second son 。一天,父亲收到了 somebody 给他两个儿子的两个红包,每一个红包里有 100 元,这个父亲想:我的两个儿子住的离我这么远,送过去的话还要花费我很多的车费,不值,先存着,等下一次收红包时一起给。(不要问为什么不网上转钱)

接着,他就给他两儿子发消息说:“儿啊,我收到了有人给你们的红包,跑去你们那太费劲,先存着”

他两儿子开到就怒了,说:“为什么?那是我们的红包!!!”

父亲说:“也不是不给,等到下次有红包在一起给,我先记着,我X年X月X日 … … 欠 … … 红包,好了,我欠条都写好了,不赖账吧。”

文中父亲懒得去把红包送去给儿子,就用了 lazy tag 先存着一个标志,等到下次询问才往下传,少花费了时间。

让我们康康如何写代码:

void jia(int i, int v) {
	t[i] += v, lz[i] += v;
}
void down(int i) {
	if(lz[i]) {
		jia(i + i, lz[i]);
		jia(i + i + 1, lz[i]);
		lz[i] = 0;
	}
}
void add(int i, int x, int y) {
	if(y < pl || x > pr) return;
	if(x >= pl && y <= pr) {
		jia(i, px);
		return;
	}
	int m = (x + y) / 2; down(i);
	add(i + i, x, m); 
	add(i + i + 1, m + 1, y);
	t[i] = max(t[i + i], t[i + i + 1]);
}

二.后记

今天的线段树就到这里。

谢谢
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值