线段树
线段树
一、线段树简介
1、简单图解及存储结构
线段树用于维护一段区间的性质,包括最值、和、最大公约数等等
除叶子结点外,是一颗满二叉树 -> 可以用堆来存 -> 可以用一维数组来存
对编号x的节点:
- 父节点:x/2向下取整,即:
x >> 1
- 左儿子:2*x,即:
x << 1
- 右儿子:2*x+1,即:
x << 1 | 1
最坏情况下:假设倒数第2层最多有n个节点,除最后一层外一共有2n-1个节点,最后一层最多有2*n个节点,所以一共最多有4n-1个节点。
2、基本操作及代码(以最大值为例)
//节点:
struct Node
{
int l, r;
int v; //区间[l,r]的最大值
}tr[4 * N];
//pushup():单点修改,由子节点更新父节点
void pushup(int u)
{
tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}
//build():初始化线段树
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
//modify():单点修改操作
void modify(int u, int x, int v)
{
if(tr[u].l == x && tr[u].r == x) tr[u].v = v;
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
}
int query(int u, int l, int r)
{
//树中节点已经完全包含在区间内
if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;
int mid = tr[u].l + tr[u].r >> 1;
int v = 0;
if(l <= mid) v = query(u << 1, l, r);
if(r > mid) v = max(v, query(u << 1 | 1, l, r));
return v;
//if(r <= mid) return query(u << 1, l, r);
//else if(l > mid) return query(u << 1 | 1, l, r);
//else return max(query(u << 1, l, r), query(u << 1 | 1, l, r));
}
//pushdown():区间修改,带懒标记(延迟更新)
//此处以区间和为例
//两个信息:sum(区间和)、add(懒标记、区间每个数需要加的值,意义:所有以当前节点为根节点的子树全部加上add,根节点不加)
void pushdown(int u)
{
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if(root.add)
{
left.add += root.add, left.sum += (left.r - left.l + 1) * root.add;
right.add += root.add, right.sum ++ (right.r - right.l + 1) * root.add;
root.add = 0;
}
}
pushup()的位置一般在:build()、modify()的结尾
pushdown()的位置一般在:modify()、query()的开头
二、相关题目
1275.最大数
维护区间最大值的线段树
245.你能回答这些问题吗
经分析的用线段树维护:区间和、最大前缀和、最大后缀和、最大连续区间和
246.区间最大公约数
线段树维护:差分数组区间和、区间最大公约数
243.一个简单的整数问题2
采用带懒标记的线段树解法,实现区间修改,区间查询
247.亚特兰蒂斯
用线段树维护扫描线,根据该题的特殊性质可推出带懒标记的线段树不需要pushdown操作
让人自闭的题目,裂开。。。。
1277.维护序列
带两个懒标记的线段树