SegmentTree-Complete 线段树完全版

线段树完全版关键词:延迟加载、懒标记Lazy Tag

单点更新的情况比较简单。请看 线段树基础版

下面说说区间更新的情况。

场景是这样的,还是刚刚的数,求区间的和。


#define lson rt<<1
#define rson rt<<1|1
#define len (r-l+1) //(l,r)区间的长度

这次是区间更新,我们要用到区间的长度

建树

build和pushUp不变。我们把树建立好打印一下:

[1]:36 [2]:26 [3]:10 [4]:15 [5]:11 [6]:10 [7]:0 [8]:6 [9]:9 [10]:5 [11]:6 [12]:8 [13]:2 [14]:0

pushDown 重点

延迟加载、懒标记思想:

假设现在要把区间【2,6】里5个数全加10,求总和。会数学的都知道,一共加了50,总和变成86。就是说对一段区间的统一操作,不需要对区间里的单个点进行改动。

只改动他们的老祖宗,并在他们的祖宗上做一个标记。以后啥事需要用到儿子了,一看父亲上有标记,就知道这个儿子还需要被更新。

下面的代码就是根据父亲rt,和对应的区间长度l,往下pushDown懒标记。

void pushDown(int rt,int len){//
    if(lazy[rt]){//这个点有lazyTag才往下下发lazyTag
                              //lazyTag里存的值是"要延迟加载的"值,例子就是10
        lazy[lson]+=lazy[rt];
        lazy[rson]+=lazy[rt];

        tree[lson]+=lazy[rt]*(len-len/2);//这里要注意
        tree[rson]+=lazy[rt]*(len/2);
        lazy[rt]=0;//清除父亲的标记
    }
}

注意第6行:举个例子来说明,rt代表的区间[1,7],长度l为7,7/2=3.

[1,7]的左右儿子是[1,4][5,7]这两个。左儿子长度为4,右儿子长度为3。

第6行那么写,就是要处理区间长度为奇数的情况,由于是完全二叉树,rt的左儿子代表的区间一定更长。

更新

void update(int L,int R,int addVal,int l,int r,int rt){
    cout<<__func__<<l<<','<<r<<','<<rt<<'n';
    //(l,r)就是根节点rt所代表的区间。
    //[L,R]是要更新的区间。
    if(L<=l&&R>=r){//(l,r)在[L,R]里
        tree[rt]+= len*addVal;//那就只更新rt,不用往下更新了。
        lazy[rt]+= addVal; //但必须做好lazy标记用于pushDown。标记的值就是要加的数。
        return;
    }
    //如果区间不能涵盖:
    pushDown(rt,r-l+1);//下放懒标记。防止以前有过改动儿子没加载
    int m = (l+r)>>1;
    if(L<=m)
        update(L,R,addVal,l,m,lson);
    if(R>m)
        update(L,R,addVal,m+1,r,rson);
    pushUp(rt);
}

我们还是通过例子看看程序是怎么执行的:

update(2,6,10,1,7,1);//2到6全加10

打印一下:

update1,7,1
update1,4,2
update1,2,4
update2,2,9
update3,4,5
update5,7,3
update5,6,6
[0]:0 [1]:86 [2]:56 [3]:30 [4]:25 [5]:31 [6]:30 [7]:0 [8]:6 [9]:19 [10]:5 [11]:6 [12]:8 [13]:2 [14]:0 [15]:0

画出图:

可以看到,框里的数,5、6、8、2虽然没有被更新,但是他们的父亲却是正确的值。

查询

下面我们通过查询,来看一下lazyTag到底啥时候排上用场。

现在我们查询区间(3,5)这三个数的和,应该是49。

其中,区间(3,4)里的两个数虽然是5、6,但是他们的和31是正确的。

但是第4个数8就不对了,应该是18才对。

看代码:

int query(int L,int R,int l,int r, int rt){//[L,R]是要查询的区间
    cout<<__func__<<" ["<<L<<','<<R<<"] ("<<l<<','<<r<<") "<<rt<<'n';
    if(L<=l&&R>=r){//如果[L,R]里有(l,r)区间,直接返回对应的根节点
        return tree[rt];
    }
    pushDown(rt,len);//下放标记派上用场了
    int m = (l+r)>>1;
    int sum = 0;
    if(L<=m){
        sum += query(L,R,l,m,lson);
    }
    if(R>=m+1){
        sum += query(L,R,m+1,r,rson);
    }
    return sum;
}

输出结果:

query [3,5] (1,7) 1
query [3,5] (1,4) 2
query [3,5] (3,4) 5
query [3,5] (5,7) 3
query [3,5] (5,6) 6
pushDown rt:6 len:2
query [3,5] (5,5) 12
49

如我们所愿,

第5行,查询区间(5,6),6不属于[3,5],

往下执行到了pushDown(6),第6行输出了pushDown rt:6 len:2

这时候我们如果再打印一下,就会发现,30的两个孩子8和2已经全部完成了延迟加 大专栏  SegmentTree-Complete 线段树完全版载,成了18和12~

[1]:76 [2]:46 [3]:30 [4]:15 [5]:31 [6]:30 [7]:0 [8]:6 [9]:9 [10]:5 [11]:6 [12]:18 [13]:12 [14]:0

线段树完全版

//线段树 求解区间最值问题
#include <iostream>
#include <vector>
#include "../Vt.h"

#define lson rt<<1
#define rson rt<<1|1
#define len (r-l+1)

using namespace std;

vector<int> vt{6,9,5,6,8,2,0};
int i = 0;
vector<int> tree(16);
vector<int> lazy(16);

void pushUp(int rt){
    tree[rt] = tree[lson]+tree[rson];
}

void build(int l,int r,int rt){
    if(l==r)//是叶节点,存数
    {
        tree[rt] = vt[i++];
        return;
    }
    int m = (l+r)>>1;
    build(l,m,lson);
    build(m+1,r,rson);
    pushUp(rt);
}

void pushDown(int rt,int l){
    if(lazy[rt]){//这个点有lazyTag才往下下发lazyTag
        cout<<__func__<<" rt:"<<rt<<" len:"<<l<<'n';
        lazy[lson]+=lazy[rt];
        lazy[rson]+=lazy[rt];

        tree[lson]+=lazy[rt]*(l-l/2);//这里要注意
        tree[rson]+=lazy[rt]*(l/2);
        lazy[rt]=0;
    }
}

void update(int L,int R,int addVal,int l,int r,int rt){
    cout<<__func__<<l<<','<<r<<','<<rt<<'n';
    //(l,r)就是根节点rt所代表的区间。
    //[L,R]是要更新的区间。
    if(L<=l&&R>=r){//(l,r)在[L,R]里
        tree[rt]+= len*addVal;//那就只更新rt,不用往下更新了。
        lazy[rt]+= addVal; //但必须做好lazy标记用于pushDown。标记的值就是要加的数。
        return;
    }
    //如果区间不能涵盖:
    pushDown(rt,r-l+1);//下放懒标记。防止以前有过改动儿子没加载
    int m = (l+r)>>1;
    if(L<=m)
        update(L,R,addVal,l,m,lson);
    if(R>=m+1)
        update(L,R,addVal,m+1,r,rson);
    pushUp(rt);
}

int query(int L,int R,int l,int r, int rt){//[L,R]是要查询的区间
    cout<<__func__<<" ["<<L<<','<<R<<"] ("<<l<<','<<r<<") "<<rt<<'n';
    if(L<=l&&R>=r){//如果[L,R]里有(l,r)区间,直接返回对应的根节点
        return tree[rt];
    }
    pushDown(rt,len);
    int m = (l+r)>>1;
    int sum = 0;
    if(L<=m){
        sum += query(L,R,l,m,lson);
    }
    if(R>=m+1){
        sum += query(L,R,m+1,r,rson);
    }
    return sum;
}

int main(){
    build(1,vt.size(),1);
    showVtwithIndex(tree);

    update(3,6,10,1,7,1);//3到6全加10
    showVtwithIndex(tree);

    cout << query(3,5,1,7,1)<<'n';
    showVtwithIndex(tree);
    return 0;
}

原文地址:https://www.cnblogs.com/liuzhongrong/p/11874776.html

时间: 2024-11-09 04:56:43

SegmentTree-Complete 线段树完全版的相关文章

杭电 HDU ACM 2795 Billboard(线段树伪装版)

Billboard Time Limit: 20000/8000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 14144    Accepted Submission(s): 6058 Problem Description At the entrance to the university, there is a huge rectangular billboard of

最大最小值(线段树基础版)

最大最小值 时间限制:1000 ms  |  内存限制:65535 KB 描述 给出N个整数,执行M次询问. 对于每次询问,首先输入三个整数C.L.R: 如果C等于1,输出第L个数到第R个数之间的最小值: 如果C等于2,输出第L个数到第R个数之间的最大值: 如果C等于3,输出第L个数到第R个数之间的最小值与最大值的和. (包括第L个数和第R个数). 输入 首先输入一个整数T(T≤100),表示有T组数据. 对于每组数据,先输入一个整数N(1≤N≤10000),表示有N个整数: 接下来一行有N个整

[您有新的未分配科技点]可,可,可持久化!?------可持久化线段树普及版讲解

最近跑来打数据结构,于是我决定搞一发可持久化,然后发现--一发不可收啊-- 对于可持久化数据结构,其最大的特征是"历史版本查询",即可以回到某一次修改之前的状态,并继续操作:而这种"历史版本查询"会衍生出其他一些强大的操作. 今天,我们主要讲解可持久化线段树.其实,它的另外一个名字"主席树"似乎更加为人所知(主席%%%). 主席树与普通的线段树相比,多出来的操作是在修改时复制修改的一条链,这个操作的过程大概长下面这样. 至于为什么要这样做-- 对

线段树普及版

一.简介线段树 \(ps\): 此处以询问区间和为例 线段树之所以称为"树",是因为其具有树的结构特性.线段树由于本身是专门用来处理区间问题的(包括\(RMQ\).\(RSQ\)问题等),所以其结构可以近似的看做一棵二叉查找树: \(emmmmm\)图是从网上偷的 对于每一个子节点而言,都表示整个序列中的一段子区间:对于每个叶子节点而言,都表示序列中的单个元素信息:子节点不断向自己的父亲节点传递信息,而父节点存储的信息则是他的每一个子节点信息的整合. 有没有觉得很熟悉?对,线段树就是分

线段树标版

1 //s d s 2 #include<cstdio> 3 #include<iostream> 4 #include<cstdlib> 5 using namespace std; 6 const int N=5000006; 7 long long a[N],sum[N];int miku[N]; 8 long long b,c,d,e; 9 10 void update(int rt) 11 { 12 sum[rt]=sum[rt<<1]+sum[r

线段树完全版【代码集合

可能有些题要重写,先放这么多 单点更新 1.hdu1166敌兵布阵 1 #include <stdio.h> 2 #define maxn 200000 3 #include <algorithm> 4 using namespace std; 5 int qr, ql, v, x, l, n, ans; 6 int tree[maxn]; 7 void build(int o, int l,int r) { 8 if (l == r) { 9 scanf("%d&quo

线段树(区间修改+区间查询)

qwq , ylx 问我要一份线段树的版 , 可我线段树一直是10分钟 ,从不写版 ,qwq ,还是放一份版在这 . 题目见:http://poj.org/problem?id=3468 1 #include <iostream> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cstdio> 5 const int inf = 1<<30 , maxn = 100000 + 11 ;

指针版线段树

只是作一下,以后必须得写数组版的...???(然而很好写? 哦对,唯一的好处就是内存少一点,没了.(coding量似乎并不会少很多?也不会多很多?雾) 还有很重要的一点就是慢...(尽管虽然没有慢多少?该卡还是卡?) 哎呀真是好纠结... 问了些神犇,似乎大家并不知道线段树还能用数组写... 呵呵... 然后看了一眼内存,指针严格开2n-1就好,而数组其实是要开4n的.... COJ上的数据太水了,数据只有大概... 所以呢.....要不我先用指针写几次再说? 不过是真心写着舒服. 1 #inc

求逆序对(线段树版)

一个序列a1,a2,a3...aN,求出满足:ai > aj 且 i < j 的个数. 一个最容易想到的方法就是枚举所有的i,j看看是否满足,显然是O(n^2)的复杂度.不够好. 可以这样考虑,开一个数组保存这n个数出现的位置和对应的次数,这个数组要开到a数组里最大的那个数MAX,也就是hash,初始状态数组里没有元素,每个数对应的个数都是0. 如果考虑第i个数,找到比它大的所有的数 的个数,查找的范围即 ai+1~MAX,这就是到i这个位置的逆序对的总和,接着把a[i]这个数添加到数组里,也