其他进行多次求解方法:
区间和:前缀和
区间最值: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]
-
特征:
- 对于线段树上的任意节点,它要么没有子节点,要么有两个子节点
- 对于一个长度为n的序列,线段树最多有 ( 2 n − 1 ) (2n-1) (2n−1)个节点
- 对于长度为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;
}