线段树 push_down 操作

线段树是一种经典的数据结构,用于处理一维区间查询和更新操作的问题。它的主要思想是将一个数组划分成若干个区间,并对每个区间建立一颗二叉树,这样就形成了一棵二叉树,即线段树。

线段树的建立过程是递归的,从根节点开始,每个节点代表一个区间,左右儿子分别代表区间的左半部分和右半部分,直到叶子节点代表的区间长度为1。每个节点保存的信息是该区间的统计信息,比如最大值、最小值、和、平均值等等。

线段树主要支持两种操作:查询和更新。查询操作用于查询一个区间内的某个统计信息,更新操作用于将一个位置的数值进行修改,并相应地更新该位置所在的区间的统计信息。具体实现时,查询和更新操作都是递归进行的,从根节点开始,逐级向下查询或更新。

线段树的时间复杂度为 O(log n),其中 n 是数组的长度。因此,线段树常常被用于需要频繁进行区间查询或更新的场合,比如区间最值、区间和、区间平均值等等。

如实现区间查询和单点修改操作,一般情况不需要push_down操作,若实现区间查询和区间修改,一般需要push_down操作来下放lazy_tag;

当需要对线段树上某一区间进行修改操作时,为了避免递归更新的开销,通常采用下推(push_down)操作。push_down操作将待更新的信息从父节点传递到子节点,在更新时可以直接访问子节点,避免了递归更新的开销。

以区间加法操作为例,下推操作的过程如下:

  1. 将父节点的待更新信息(这里是区间加法操作的加数)传递到左右儿子节点中。

  2. 左右儿子节点将其待更新信息与自己的待更新信息合并。

  3. 左右儿子节点分别更新自己的值。

  4. 清空父节点的待更新信息。

需要注意的是,下推操作必须保证子节点的值与待更新信息已经合并,否则可能会导致错误的查询结果。

下面是一段实现了区间修改和区间查询的线段数代码

#include<iostream>
#include<cstdio>
#include<cstring>
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操作仍有一些边界细节没有想通,希望再次复习的时候可以理解的更透彻一点。 

猜你喜欢

转载自blog.csdn.net/m0_66252872/article/details/129829865