详解——线段树(用法合集)

其他进行多次求解方法:

区间和:前缀和
区间最值:ST表

修改与查询混合:

区间和:树状数组
区间最值:线段树

线段树

用途:处理区间问题
求和、最值、gcd(可合并特征)

概念

分治思想+二叉树结构+lazy-tag
一棵二叉树

  • 二叉树上的节点:表示一个连续区间内的总和(线段)

  • 通过分治划分得到节点

    • [1,n]的区间为树的根节点,
    • m i d = ( 1 + n ) / 2 mid=(1+n)/2 mid=(1+n)/2,左子树 [ 1 , m i d ] [1,mid] [1,mid],右子树 [ m i d + 1 , n ] [mid+1,n] [mid+1,n]
    • 请添加图片描述
  • 特征:

    1. 对于线段树上的任意节点,它要么没有子节点,要么有两个子节点
    2. 对于一个长度为n的序列,线段树最多有 ( 2 n − 1 ) (2n-1) (2n1)个节点
    3. 对于长度为n的序列,线段树的树高为 l o g ( n ) log(n) log(n)

————————————————————————————————————————————————————————
核心:将若干个节点信息进行合并,能够得到一个连续区间的信息

1.线段树 建树

递归建树

  • 如果当期列只有一个元素,叶子节点
  • 不止一个,分成两部分,左子树 [ l , ( l + r ) / 2 ] [l, (l+r)/2] [l,(l+r)/2],右子树 [ ( l + r ) / 2 + 1 , r ] [(l+r)/2+1, r] [(l+r)/2+1,r]
const int N = 1e5+5;
int a[N];//原数列的元素
int w[N<<2];//线段树上的节点(每个节点表示一段区间信息)

//合并
void pushup(int u){
	//用数组存储树,节点编号为x的节点左孩子为2x,右孩子为2x+1
	w[u] = w[2*u]+w[2*u+1];
}
//建树
void build(int u, int l, int r){
	//u-当前访问的线段树的节点编号
	//l, r 数列的范围
	if(l == r){
		w[u] = a[l];
		return ;
	}
	//不止一个元素,拆成两部分\
	int mid = (l+r)/2;
	//[l, mid]
	build(2*u, l, mid);
	//[mid+1, r]
	build(2*u+1, mid+1, r)//合并两个孩子节点的信息,更新当前节点的信息
	pushup(u);
}

2.线段树 单点修改+查询

请添加图片描述

//单点查询
ll query1(int u, int l, int r, int p){
	//u 当前节点编号;[l, r]当前节点对应的数列范围;p 查询位置
	if(l == r){//叶子节点
		return w[u];
	}
	int mid=(l+r)/2;
	if(p <= mid){
		return query1(2*u, l, mid, p);
	} else {
		return query1(2*u+1, mid+1, r, p);
	}
}
//单点修改
void updae1(int u, int l, int r, int p, ll x){
	//将数列a[p]替换为x
	if(l == r){
		w[u] = x;
		//根据有些题目要求要改成w[u] += x,即加上x
		return ;
	}
	int mid = (l+r)/2;
	if(p <= mid){
		return update1(2*u, l, mid, p, x);
	} else {
		return update1(2*u+1, mid+1, r, p, x);
	}
	pushup(u);//更新当前节点区间信息
}

3.线段树 查询与维护区间

区间查询与区间维护的关系:

  • 查询区间 包含 维护区间:直接使用当前节点信息
  • 查询区间 与 维护区间 相交:继续递归孩子节点
  • 查询区间 与 维护区间 不相交:不再探索,返回
//区间查询

bool inRange(int L, int R, int l, int r){
	//判断[L, R] 是否被[l, r] 包含
	return l <= L && R <= r;
}
 bool outofRange(int L, int R, int l, int r){
 	//判断[L, R] 与 [l, r] 相交
 	return (r<L) || (R<l)}
 
ll query(int u, int L, int R, int l, int r){
 	//u-线段树节点编号, [L, R]当前节点维护范围
 	//[l, r] 查询区间范围
 	if(inRange(L, R, l, r)){//包含
 		return w[u];
	} else if(!outofRange(L,R,l,r)){//相交
		int mid = (L+R)/2;
		return query(2*u, L, mid, l, r)+query(2*u+1, mid+1, R, l, r);
	} else {//不相交
		return 0;
	}
}

4.线段树 区间修改

引入延迟标记(lazy-tag

int lzy[N<<2]//延迟标记


void maketag(int u, int len, ll x){
	//u-当前线段树的节点编号 len-当前线段树节点维护的区间的大小
	//x-区间修改的值
	lzy[u] += x;
	w[u] += lwn*x;
}

void pushdown(int u, int l, int r){//延迟标记下放(向下传递)
	//u-当前线段树的节点编号 [l,r]搜维护区间
	int mid=(l+r)/2;
	//[l, mid]
	maketag(2*u, mid-l+1, lzy[u]);
	//[mid+1, r]
	maketag(2*u+1, r-mid, lzy[u]);
	lzy[u] = 0;
}
//区间修改
void update(int u, int L, int R, int l, int r, ll x){
	//u-当前节点编号 [L, R]节点维护的区间
	//[l,r]修改的区间
	//x-修改的值
	if(intRange(L, R, l, r)){//包含
		maketag(u, R-L+1, x);
	} else if(!outofRange(L, R, l, r)){//相交
		int mid = (l+r)/2;
		pushdown(u, L, R);
		update(2*u, L, mid, l, r, x);
		update(2*u+1, mid+1, R, l, r, x);
		pushup(u);
	} else {//不相交
		//什么也不用做
	}
}

有一个地方需要注意一下,我们在区间查询的代码中要顺带进行延迟标记

ll query(int u, int L, int R, int l, int r){
 	if(inRange(L, R, l, r)){
 		return w[u];
	} else if(!outofRange(L,R,l,r)){
		int mid = (L+R)/2;
		pushdown(u, L, R);//!!!查询的过程中顺带下放延迟标记
		return query(2*u, L, mid, l, r)+query(2*u+1, mid+1, R, l, r);
	} else {
		return 0;
	}
}

线段树模板

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int a[N];//原数列的元素
int w[N<<2];//线段树上的节点(每个节点表示一段区间信息)
int lzy[N<<2]//延迟标记


//lazy-tag前置
void maketag(int u, int len, ll x){
	//u-当前线段树的节点编号 len-当前线段树节点维护的区间的大小
	//x-区间修改的值
	lzy[u] += x;
	w[u] += lwn*x;
}
void pushdown(int u, int l, int r){//延迟标记下放(向下传递)
	//u-当前线段树的节点编号 [l,r]搜维护区间
	int mid=(l+r)/2;
	//[l, mid]
	maketag(2*u, mid-l+1, lzy[u]);
	//[mid+1, r]
	maketag(2*u+1, r-mid, lzy[u]);
	lzy[u] = 0;
}

//合并
void pushup(int u){
	w[u] = w[2*u]+w[2*u+1];
}
//建树
void build(int u, int l, int r){
	//u-当前访问的线段树的节点编号
	//l, r 数列的范围
	if(l == r){
		w[u] = a[l];
		return ;
	}
	//不止一个元素,拆成两部分\
	int mid = (l+r)/2;
	//[l, mid]
	build(2*u, l, mid);
	//[mid+1, r]
	build(2*u+1, mid+1, r)//合并两个孩子节点的信息,更新当前节点的信息
	pushup(u);
}

//单点查询
ll query1(int u, int l, int r, int p){
	//u 当前节点编号;[l, r]当前节点对应的数列范围;p 查询位置
	if(l == r){//叶子节点
		return w[u];
	}
	int mid=(l+r)/2;
	if(p <= mid){
		return query1(2*u, l, mid, p);
	} else {
		return query1(2*u+1, mid+1, r, p);
	}
}
//单点修改
void updae1(int u, int l, int r, int p, ll x){
	//将数列a[p]替换为x
	if(l == r){
		w[u] = x;
		return ;
	}
	int mid = (l+r)/2;
	if(p <= mid){
		return update1(2*u, l, mid, p, x);
	} else {
		return update1(2*u+1, mid+1, r, p, x);
	}
	pushup(u);//更新当前节点区间信息
}

//区间查询

bool inRange(int L, int R, int l, int r){
	//判断[L, R] 是否被[l, r] 包含
	return l <= L && R <= r;
}
 bool outofRange(int L, int R, int l, int r){
 	//判断[L, R] 与 [l, r] 相交
 	return (r<L) || (R<l)}
 
ll query(int u, int L, int R, int l, int r){
 	//u-线段树节点编号, [L, R]当前节点维护范围
 	//[l, r] 查询区间范围
 	if(inRange(L, R, l, r)){//包含
 		return w[u];
	} else if(!outofRange(L,R,l,r)){//相交
		int mid = (L+R)/2;
		pushdown(u, L, R);//!!!查询的过程中顺带下放延迟标记
		return query(2*u, L, mid, l, r)+query(2*u+1, mid+1, R, l, r);
	} else {//不相交
		return 0;
	}
}


//lazy-tag区间修改
void update(int u, int L, int R, int l, int r, ll x){
	//u-当前节点编号 [L, R]节点维护的区间
	//[l,r]修改的区间
	//x-修改的值
	if(intRange(L, R, l, r)){//包含
		maketag(u, R-L+1, x);
	} else if(!outofRange(L, R, l, r)){//相交
		int mid = (l+r)/2;
		pushdown(u, L, R);
		update(2*u, L, mid, l, r, x);
		update(2*u+1, mid+1, R, l, r, x);
		pushup(u);
	} else {//不相交
		//什么也不用做
	}
}
int main(){
	
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值