线段树是一种经典的数据结构,用于处理一维区间查询和更新操作的问题。它的主要思想是将一个数组划分成若干个区间,并对每个区间建立一颗二叉树,这样就形成了一棵二叉树,即线段树。
线段树的建立过程是递归的,从根节点开始,每个节点代表一个区间,左右儿子分别代表区间的左半部分和右半部分,直到叶子节点代表的区间长度为1。每个节点保存的信息是该区间的统计信息,比如最大值、最小值、和、平均值等等。
线段树主要支持两种操作:查询和更新。查询操作用于查询一个区间内的某个统计信息,更新操作用于将一个位置的数值进行修改,并相应地更新该位置所在的区间的统计信息。具体实现时,查询和更新操作都是递归进行的,从根节点开始,逐级向下查询或更新。
线段树的时间复杂度为 O(log n),其中 n 是数组的长度。因此,线段树常常被用于需要频繁进行区间查询或更新的场合,比如区间最值、区间和、区间平均值等等。
如实现区间查询和单点修改操作,一般情况不需要push_down操作,若实现区间查询和区间修改,一般需要push_down操作来下放lazy_tag;
当需要对线段树上某一区间进行修改操作时,为了避免递归更新的开销,通常采用下推(push_down)操作。push_down操作将待更新的信息从父节点传递到子节点,在更新时可以直接访问子节点,避免了递归更新的开销。
以区间加法操作为例,下推操作的过程如下:
将父节点的待更新信息(这里是区间加法操作的加数)传递到左右儿子节点中。
左右儿子节点将其待更新信息与自己的待更新信息合并。
左右儿子节点分别更新自己的值。
清空父节点的待更新信息。
需要注意的是,下推操作必须保证子节点的值与待更新信息已经合并,否则可能会导致错误的查询结果。
下面是一段实现了区间修改和区间查询的线段数代码
#include
#include
#include
using namespace std;
const int N = 100010;
int n,m;
int w[N];
typedef long long LL;
struct node
{
int l,r;
LL add,v;
}tr[N * 4];
void push_up(int u)
{
tr[u].v = tr[u << 1].v + tr[u << 1 | 1].v;
}
void push_down(int u)
{
auto &root = tr[u],&left = tr[u << 1],&right = tr[u << 1 | 1];
if(root.add)
{
left.add += root.add,left.v += (left.r - left.l + 1) * root.add;
right.add += root.add,right.v += (right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u,int l, int r)
{
tr[u] = {l, r};
if(l == r) tr[u].v = w[l];
else
{
int mid = l + r >> 1;
build(u << 1, l, mid),build(u << 1 | 1, mid + 1, r);
push_up(u);
}
}
void modify(int u,int l,int r,int d)
{
if(tr[u].l >= l && tr[u].r <= r)
{
tr[u].v += (tr[u].r - tr[u].l + 1) * d;
tr[u].add += d;
}
else
{
push_down(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, d);
if(r > mid)modify(u << 1 | 1, l ,r ,d );
push_up(u);
}
}
LL query(int u,int l,int r)
{
if(tr[u].l >= l && tr[u].r <= r)
{
return tr[u].v;
}
else
{
LL res = 0;
push_down(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res += query(u << 1, l, r);
if(r > mid) res += query(u << 1 | 1, l, r);
return res;
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) cin >> w[i];
build(1, 1, n);
char op[2];
int l, r;
while( m -- )
{
scanf("%s",op);
if(op[0] == 'Q')
{
cin >> l >> r;
printf("%lld\n",query(1, l ,r));
}
else
{
int d;
cin >> l >> r >> d;
modify(1, l, r, d);
}
}
return 0;
}
这次复习数据结构,可以做到能手撸带lazy_tag的线段树并应用到简单题目中,但是push_down操作仍有一些边界细节没有想通,希望再次复习的时候可以理解的更透彻一点。