线段树(单点更新以及区间更新模版)

今天来讲一下我的线段树学习历程。

线段树的模版算法,就是将所给的数据存储在一个树中,可以快速的查找一个区间内的最值或一个区间的和,并支持单点或者区间的修改。

接下来先来讲一下最基础的单点修改以及查找最值

给定一组数字,假设我们需要查找某个区间的最值。

先来画一个图

其中每个方格上的两个数字表示区间。

接下来,我们假设这六个数的值分别为1,1,4,5,1,4

那么我们用红色的数将每个区间的最值表示在旁边

 那么很显然,当我们要查询形如(4,6)的区间时,我们可以轻松得出答案。

那如果我们要查询的区间时(3,5)呢?

这时我们可以看出,(3,5)实际上是(3,3)和(4,5)的组合。

那么我们只需不断递归区间,当此时的区间被包含在查询区间内时,我们便返回这个值,并在回溯时记录最大值即可。

那么先来给出最基本的建树函数

const int maxn=1000005;
int tr[maxn];//这里用来记录每个区间节点的最值
int a[maxn];//这里用来记录n个数字,比如上图中的1,1,4,5,1,5
void build(int l,int r,int id)
{
    if(l==r)//叶子节点
    {
        tr[id]=a[l];
    }
    int mid=(l+r)/2;
    build(l,mid,id*2);//递归左儿子
    build(mid+1,r,id*2+1);//递归右儿子
    tr[id]=max(tr[id*2],tr[id*2+1));//记录最值
}

这里的id实际上是每个节点的标号。显然左儿子为id*2,右儿子为id*2+1

那么接下来,我们来查找每个区间的最值

int find(int l,int r,int x,int y,int id)
{
	if(l>=x&&r<=y)//如果被查询区间包含
	{
		return tr[id];
	}
	int ans=-1000000000;
	int mid=(l+r)/2;
	if(x<=mid)//如果x大于mid,则无需递归左区间
	{
		ans=max(ans,find(l,mid,x,y,id*2));
	}
	if(y>mid)//如果y小于mid,则无需递归右区间
	{
		ans=max(ans,find(mid+1,r,x,y,id*2+1));
	}
	return ans;
}

那么我们该如何实现单点修改呢?

假设我们将1,1,4,5,1,4改为了1,1,4,6,1,4

我们先一路找到第4个树的叶子节点,发现是(4,4),我们将tr[id]改为6

接下来,我们一路向上更新其父节点

怎么样,是不是其实很简单?

void ddgx(int id,int l,int r,int x,int v)//x为要修改的点,v为被修改成的值
{
	if(l==r)
	{
		tr[id]=v;
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid)//在左区间
	{
		ddgx(id*2,l,mid,x,v);
	}
	else//在右区间
	{
		ddgx(id*2+1,mid+1,r,x,v);
	}
	tr[id]=max(td[id*2],td[id*2+1]);
}

那么接下来讲一下区间更新

实际上,其实对区间的每个点单点更新也能实现区间更新,但这样显然速度太慢了

那么我们要用到的就是懒标记(lazy-tag)

其实我在网上看了很多有关懒标记的理解,但我感觉还是都太抽象了,我尽量给大家讲明白。

我们假设一个lazy[id],他的含义是对于id这个区间,对区间加上的值。

比如一个lazy[id]表示(1,6)区间,那么显然对于sum,有sum[id]=(6-1+1)*lazy[id]

那么我们得到了第一个基本的公式sum[id]=lazy[id]*(r-l+1)

那么最基础的问题来了,为什么我们需要这个lazy?

我们来画个图理解一下

首先依旧是1,1,4,5,1,4

我们用红笔表示lazy,用蓝笔表示sum

现在我们要在(1,6)区间加上1

那么显然,如果我们对每个点都处理,显然是太麻烦了

于是我们只对(1,6)进行lazy和sum的改变(因为很懒啊)

于是就变成了

哈哈,这时就有人要问了,如果接下来我要修改(1,3)怎么办???

我们假设对(1,3)加2

因为我在之前没有对(1,3)进行修改,但他其实被改变了

那我们直接开始下放!!!

比如我们先查看(1,6),这时我们发现它没有包含(1,3)

于是我们将(1,6)携带的lazy进行下放

我们对(1,3)与(4,6),让他继承(1,6)的lazy

并让(1,3)和(4,6)的sum改变

这时因为(1,6)的lazy被下放了,所以将(1,6)的lazy改为0

这时我们向下递归,先是查到(1,3),这时我们发现已经被包含了

于是向上一步那样,我们开始修改

 

到了这里相信大家已经明白了

我们需要进行的无非就是不断的下放,知道遇到恰好被包含的区间即可

接下来给出代码

void push_down(int id,int l,int r)//下放操作
{
	if(lazy[id])
	{
		int mid=(l+r)/2;
		lazy[id*2]+=lazy[id];
		lazy[id*2+1]+=lazy[id];
		sum[id*2]+=lazy[id]*(mid-l+1);
		sum[id*2+1]+=lazy[id]*(r-mid);
		lazy[id]=0;
	}
}
void qjgx(int l,int r,int x,int y,int k,int id)
{
	if(l>=x&&r<=y)//遇到被包含区间
	{
		lazy[id]+=k;
		sum[id]+=k*(r-l+1);
		return;
	}
	push_down(id,l,r);
	int mid=(l+r)/2;
	if(x<=mid)
	{
		qjgx(l,mid,x,y,k,id*2);
	}
	if(y>mid)
	{
		qjgx(mid+1,r,x,y,k,id*2+1);
	}
	sum[id]=sum[id*2]+sum[id*2+1];//注意在回溯时重新记录一下
}

 最后一步便是区间查找了

还是一样,如果恰好查到最好,查不到我就下放呗

int find(int l,int r,int x,int y,int id)
{
	if(l>=x&&r<=y)
	{
		return sum[id];
	}
	push_down(id,l,r);
	int mid=(l+r)/2;
	int ans=0;
	if(x<=mid)
	{
		ans+=find(l,mid,x,y,id*2);
	}
	if(y>mid)
	{
		ans+=find(mid+1,r,x,y,id*2+1);
	}
	return ans;
}

这就是线段树的几种基本模版

希望能帮到大家

 

### 线段树算法实现 线段树是一种高效的数据结构,能够快速处理数组上的单点修改和区间查询操作。以下是基于 Python 的线段树模板代码,支持单点更新区间查询功能。 #### 1. 初始化线段树 构建线段树时,通常会采用递归的方式初始化节点范围及其对应的值。 ```python class SegmentTree: def __init__(self, data): self.n = len(data) self.tree = [0] * (4 * self.n) # 创建大小为4n的数组存储线段树 self.build(0, 0, self.n - 1, data) def build(self, node, start, end, data): if start == end: # 叶子节点 self.tree[node] = data[start] else: mid = (start + end) // 2 left_child = 2 * node + 1 right_child = 2 * node + 2 self.build(left_child, start, mid, data) # 构建左子树 self.build(right_child, mid + 1, end, data) # 构建右子树 self.tree[node] = self.tree[left_child] + self.tree[right_child] # 合并左右子树的结果 ``` #### 2. 单点更新 当某个位置的值发生变化时,可以通过递归找到对应叶子节点并更新其父节点的值。 ```python def update_point(self, idx, value): self.update_node(0, 0, self.n - 1, idx, value) def update_node(self, node, start, end, idx, value): if start == end: # 找到目标叶子节点 self.tree[node] = value else: mid = (start + end) // 2 left_child = 2 * node + 1 right_child = 2 * node + 2 if start <= idx <= mid: # 更新左侧子树 self.update_node(left_child, start, mid, idx, value) else: # 更新右侧子树 self.update_node(right_child, mid + 1, end, idx, value) self.tree[node] = self.tree[left_child] + self.tree[right_child] # 维护当前节点的值 ``` #### 3. 区间查询 通过递归方式查找指定区间的总和或其他聚合函数结果。 ```python def query_range(self, l, r): return self.query_node(0, 0, self.n - 1, l, r) def query_node(self, node, start, end, l, r): if r < start or end < l: # 当前区间完全不重叠 return 0 if l <= start and end <= r: # 完全覆盖 return self.tree[node] mid = (start + end) // 2 left_child = 2 * node + 1 right_child = 2 * node + 2 p1 = self.query_node(left_child, start, mid, l, r) # 查询左子树 p2 = self.query_node(right_child, mid + 1, end, l, r) # 查询右子树 return p1 + p2 ``` 以上实现了线段树的核心功能——单点更新区间查询[^1]。 --- ### 使用示例 以下是一个简单的例子展示如何使用上述线段树类来完成单点更新区间查询的操作: ```python data = [1, 3, 5, 7, 9, 11] st = SegmentTree(data) print(st.query_range(1, 3)) # 输出:15 (3+5+7) st.update_point(2, 6) # 将索引2处的值从5改为6 print(st.query_range(1, 3)) # 输出:16 (3+6+7) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值