浅谈线段树2

文章介绍了线段树在处理区间修改和区间查询时如何利用懒标记优化算法,通过`spread`函数下放修改量,减少操作次数。区间修改通过递归和懒标记实现,查询操作中同样需要处理懒标记。这种方法提高了效率,将操作复杂度从线性降低到对数级别。
摘要由CSDN通过智能技术生成

之前我们讲过线段树的单点修改和区间查询,现在我们来谈一下区间修改和区间查询。

这里是上一篇博客链接:浅谈线段树2

  • 区间修改

假如我们对区间[L, R]内的a[i]全部进行+x的操作,这里我们就需要进行区间修改的操作,话不多说先看图:

定义一个数组lazytag[4*N],这个数组存的是每一个子树序点所对应的区间修改的修改量。

void spread(int node, int l, int r) {
        if (lazytag[node]) {
            lazytag[lson] += lazytag[node];
            lazytag[rson] += lazytag[node];
            int mid = (l + r) / 2;
            sum[lson] += 1LL * lazytag[node] * (mid - l + 1);
            sum[rson] += 1LL * lazytag[node] * (r - mid);
            lazytag[node] = 0;
        }
    }

spread函数中,参数node表示树的节点序,l和r分别表示node区间代表的区间[l, r]。这个函数的操作是什么呢?

如果lazytag[node]不为0,那么就可以将该区间二分,下放需要加的x,同时改变左子树和右子树的sum。左子树的改变区间为[l, mid],即它需要加上(mid - l + 1)个x,同理右子树相同。但是下放x后,需要把lazytag[node]置0。

提出问题:我们已知使用懒标记能使区间修改的操作更快,那么它快在哪呢?

回答问题:这里的区间修改操作是一个对区间整体的改变,没有下放到单点!如果直接对线性的数组进行区间修改,那么需要的操作应该是(r - l + 1)次,然而如果用线段树加懒标记维护一下那么只需要log(r - l + 1)次操作。

补充:如果想使修改量下放到单点,那么可以进行一次单点查询和懒标记下放操作,代码如下

//单点查询
    ll query(int node, int l, int r, int idx) {
        spread(node, l, r);
        //区间修改的懒标记不是完全下放
        //它是区间下放
        if (l == r) {
            return sum[node];
        }
        //spread(node, l, r);
        else {
            int mid = (l + r) / 2;
            if (idx <= mid)
            return     query(lson, l, mid, idx);
            else
            return    query(rson, mid + 1, r, idx);
        }

在每次开始查询前都要将子树序点为node处的懒标记具体下放至lson和rson。

回归正题,知道懒标记这一方法后,我们来看看区间修改的操作:

//change
    void change(int node, int l, int r, int L, int R, int x) {
        if (l == L and r == R) {
            lazytag[node] += x;
            sum[node] += 1ll*x*(r - l + 1);
            return;
    }
        spread(node, l, r);
        int mid = (l + r) / 2;
        if (R <= mid) {
            change(lson, l, mid, L, R, x);
        }
        else if (L > mid) {
            change(rson, mid + 1, r, L, R, x);
        }
        else {
            change(lson, l, mid, L, mid, x);
            change(rson, mid + 1, r, mid + 1, R, x);
        }
        push_up(node);
    }

看一看这串代码:函数中的参数,node,l,r 是一块的,代表子树节点node所代表的区间(l,r)。之后的L,R,x代表对在区间(L,R)内的数进行+x操作。这显然是个递归操作,当(l==L and r==R)时return,因为此时就能找到一个节点所代表的区间(l,r)完全被修改。倘若没有找到这样的区间,即本来的node节点与它代表的区间不符合要求,那么接下来就下放懒标记,将它下放到node的左子树和右子树上。下放后再二分区间通过L,R和mid的关系找到对应的区间去进行修改。记住每次对一个区间进行修改后都要去进行更新sum的操作。

  • 查询操作

修改完后就是进行查询操作了!

为什么要讲查询操作呢?因为区间修改和普通的单点修改后的查询操作很相似,但是却有不一样的地方:

long long query(int node, int l, int r, int L, int R) {//查询操作
        //查询l,r内的下标为node的sum,并返回sum的值 
        if (l == L && r == R) {
            return sum[node];
        }
        spread(node, l, r);
        int mid = (l + r) >> 1;
        if (R <= mid) {
            return query(lson, l, mid, L, R);
        }
        else if (L > mid) {
            return query(rson, mid + 1, r, L, R);
        }
        else {
            long long ans = 0;
            ans += query(lson, l, mid, L, mid);
            ans += query(rson, mid + 1, r, mid + 1, R);
            return ans;
        }
    }

发现不同了吗?这儿的每次查询都要下放懒标记,这和前面提到过的单点查询的原理是相同的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值