Segment tree (important! More understanding of lazy tags!)

Basic concept:

The line segment tree corresponding to the line segment (interval) [L, R] is a binary tree composed of the interval [L, R] and its sub-intervals (as shown in the figure below)

The characteristics of the line segment tree: 

(1) The leaf node of the line segment tree is an interval with only one element, so the line segment tree corresponding to the interval with length n has n leaf nodes;

(2) When constructing a line segment tree, each interval is divided into two parts, and the difference between the lengths of the two parts does not exceed 1, so the leaf nodes of the line segment tree appear in the last layer or the penultimate layer, so we can Yes, the height of the line segment tree does not exceed [\log_{2}n ]+1;

The line segment tree is approximate to a full binary tree, so the line segment tree can be represented by the array representation of the binary tree. For an interval of length n, since the height of the corresponding line segment tree is [\log_{2}n ]+1, it can be seen from (2) that when the line segment tree is represented by an array, the upper bound of the size of the array is

                2^{[\log_{2}n]+1} - 1 < 2^{[\log_{2}n]+1+1} = 2^{\log_{2}n}*2^{2} = 4n

That is, the size of the array does not exceed 4n. In addition, each node of the line segment tree represents an interval, and the data value stored in the line segment tree node is the sum of the interval.

The main function of the line segment tree 

The line segment tree is suitable for problems related to interval statistics. If you need to dynamically update the data in the interval and perform interval query, then using the line segment tree can achieve faster update and query speed. 

Updating the line segment tree 

Single point update:

Changing the value of an element means changing the value of the leaf node, and the sum of all nodes from the leaf node to the root node must be updated.

Interval update:

 

 

 The lazy mark is like a father escrowing pocket money for his son. When we do lazy modification, when the modified range completely covers the child node range, first modify the sum value of the child node range, then mark it with a lazy mark, and then return immediately, regardless of the update of the children below. When the download is needed, the parent node will download the lazy mark (pocket money).

 Full code display:

1. Array type definition:

#include<iostream>
#include<map>
#include<stack>
using namespace std;

//线段树近似于满二叉树,因此可以用二叉树的数组表示法表示线段树。
constexpr auto N = 1000;
//sum[i]为结点i所对应区间中元素的和,初始值为0;lazy[i]为结点i的懒惰结点(延时更新值)
//在后面线段树操作中采用延时更新时使用该数组,初始值为0
int sum[N << 2], lazy[N << 2];

//根据其类似二叉树的性质,由自底向上的方法构造线段树
//当一个结点的孩子更新完毕后,需要更新该结点到根结点上所有结点的值,即需要更新当前结点的信息,成为向上更新函数
void pushUp(int rt) {
	sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; 
}
//根据数组a构建表示区间和线段树,rt表示当前结点编号,l,r表示当前结点区间
//该算法类似于后根遍历
void build(int a[N], int rt, int l, int r) {
	if (l == r) { //达到叶结点
		sum[rt] = a[l]; //叶结点中只有一个元素,元素和即为该元素的值
		return;
	}
	int mid = (l + r) >> 1;
	build(a, rt << 1, l,mid); //构建左子树
	build(a, (rt << 1) | 1, mid + 1, r); //构建右子树,这里(rt << 1) | 1相当于rt=2*rt+1;
	pushUp(rt); //根据孩子结点信息更新当前结点的信息
}

/*
	线段树的更新有两种类型:单点更新(改变一个元素的值)和区间更新(改变一个下标区间内所有元素的值)
	单点更新:
		改变一个元素的值即改变叶结点的值,则从叶结点到根结点的所有结点的和都要更新。
*/
//单点更新,将a[pos]的值增加inc    (递归+值更新)
void pointUpd(int rt, int l, int r, int pos, int inc) {
	if (l == r) {  //达到叶结点,此时l=r=pos
		sum[rt] += inc; //修改叶结点的值
		return;
	}
	int mid = (l + r) >> 1;
	if (pos <= mid)pointUpd(rt << 1, l, mid, pos, inc);//pos在左子树
	else pointUpd(rt << 1 | 1, mid + 1, r, pos, inc);//pos在右子树
	pushUp(rt); //更新当前结点到根节点的所有结点
}

//将结点rt的延时更新值向下传递。参数ln、rn分别为结点rt的左子树和右子树所对应区间的长度
void pushDown(int rt, int ln, int rn) {
	if (lazy[rt]) {
		lazy[rt << 1]+=lazy[rt]; //当前结点延迟更新值传递给左孩子
		lazy[(rt << 1) | 1] += lazy[rt]; //当前结点延迟更新值传递给右孩子
		//利用当前结点rt的延时更新值修改rt的孩子结点的sum
		sum[rt << 1] += lazy[rt] * ln;
		sum[rt << 1 | 1] += lazy[rt] + rn;
		lazy[rt] = 0; //清除本节点的延时更新值
	}
}
//区间更新,将a在区间[L,R]中的元素值都增加inc
//由于采用延时更新,因此在更新到达终止节点就不在向下更新
void rangeUpd(int rt, int l, int r, int L, int R, int inc) {
	if (L <= l && r <= R) {  //包含,结点[l,r]为中直接点
		sum[rt] += inc * (r - l + 1); //更新当前区间的和,当前区间的每一个元素都增加inc
		lazy[rt] += inc; //更新当前结点的延时更新值
		return;
	}
	int mid = (r + l) >> 1;
	pushDown(rt, mid - l + 1, r - mid); //更新当前结点的sum,并将延时更新值向孩子结点传递
	//判断左右子树与[L,R]有无交集,若有交集则继续向下搜索
	if (L <= mid) rangeUpd(rt<<1,l,mid,L,R,inc);
	if (R > mid)rangeUpd((rt << 1) | 1, mid + 1, r, L, R, inc);
	pushUp(rt); //更新当前结点到根结点的所有结点

}

/*
	查询方法:
	从根节点出发对线段树进行先根遍历,如果遇到终止节点,则可以直接返回该结点的sum,
	最终结果为所有终止节点的sum的和。
	注意在对一个结点进行处理前需要嗲用函数pushDown将该节点的延时值向下传递
*/
//线段树的查询操作,查询下标区间L~R中元素的和,结果作为函数的返回值
int query(int rt, int l, int r, int L, int R) {
	if (L <= l && r <=R) //判断结点l~r为终止节点
		return sum[rt];
	int mid = (l + r) >> 1;
	pushDown(rt, mid - l + 1, r - mid); //更新当前结点的sum,并将延时更新值向孩子结点传递
	int ret = 0; 
	if (L <= mid) ret += query(rt << 1, l, mid, L, R);
	if (R > mid)ret += query((rt << 1) | 1, mid + 1, r, L, R);
	return ret;
}


int main() {
	int a[] = { 2,5,3,4,1,6,8,9,7,3 };
	build(a, 1, 0, 9);
	pointUpd(1, 0, 9, 7, 5);
	rangeUpd(1, 0, 9, 2, 5, 5);
	cout<<query(1, 0, 9, 0,9);
}

2. Structure definition

#include<iostream>
using namespace std;

const int maxn = 1e5;
typedef long long ll;

struct node {
    ll left, right; //左右端点。
    ll sum;//区间[left,right]的和
    ll lazy;//lazy标记。
}SegTree[maxn << 2];

//建树和普通线段树是没有什么区别的
void BuildTree(int rt, int l, int r) {
    SegTree[rt].left = l, SegTree[rt].right = r;
    SegTree[rt].lazy = 0;
    if (l == r) {
        cin >> SegTree[rt].sum;//赋值。
        return;
    }
    BuildTree(rt << 1, l, (l + r) >> 1);   //递归建立左子树
    BuildTree(rt << 1 | 1, ((l + r) >> 1) + 1, r);//递归建立右子树。
    SegTree[rt].sum = SegTree[rt << 1].sum + SegTree[rt << 1 | 1].sum;
}

//Pushdown函数(lazy标记下移)
void PushDown(int rt) {
    //rt代表要下移的父结点。
    if (SegTree[rt].lazy) {
        //如果是有标记的。
        SegTree[rt << 1].lazy += SegTree[rt].lazy;//标记下移给左孩子。
        SegTree[rt << 1 | 1].lazy += SegTree[rt].lazy;//标记下移给右孩子。
        //左右孩子将欠下的给补上。
        SegTree[rt << 1].sum += (SegTree[rt << 1].right - SegTree[rt << 1].left + 1) * SegTree[rt].lazy;
        SegTree[rt << 1 | 1].sum += (SegTree[rt << 1 | 1].right - SegTree[rt << 1 | 1].left + 1) * SegTree[rt].lazy;
        SegTree[rt].lazy = 0;//把欠的给清除。
    }
}

//Update更新函数
void UpDate(int rt, int c, int l, int r) {
    //对区间修改的函数也同样可以对单点进行修改。
    if (SegTree[rt].left == l && SegTree[rt].right == r) {
        SegTree[rt].lazy += c; //记下标记,
        SegTree[rt].sum += (SegTree[rt].right - SegTree[rt].left + 1) * c;
        return;          //停止递归。
    }
    if (SegTree[rt].left == SegTree[rt].right) {
        //到了叶子结点,不能往下了,也返回。
        return;
    }
    PushDown(rt);//到了这步发现区间没有全覆盖我们自然要先标记下移,再去寻找左右孩子
    int mid = (SegTree[rt].left + SegTree[rt].right) / 2;
    if (r <= mid) {
        //更新区间全部在左孩子。
        UpDate(rt << 1, c, l, r);
    }
    else if (l > mid) {
        //更新区间全部在右孩子。
        UpDate(rt << 1 | 1, c, l, r);
    }
    else {
        //否则左右区间都有。
        UpDate(rt << 1, c, l, mid); //更新左孩子
        UpDate(rt << 1 | 1, c, mid + 1, r); //更新右孩子。
    }
    SegTree[rt].sum = SegTree[rt << 1].sum + SegTree[rt << 1 | 1].sum;
}

//QueryTree函数(区间查询)
ll QueryTree(int rt, int l, int r) {
    //区间查询,对[l,r]区间查询
    if (SegTree[rt].left == l && SegTree[rt].right == r) {
        return SegTree[rt].sum;
    }
    PushDown(rt);
    int mid = (SegTree[rt].right + SegTree[rt].left) >> 1;
    ll ans = 0;
    if (r <= mid) {
        //说明全部在左孩子
        ans += QueryTree(rt << 1, l, r);
    }
    else if (l > mid) {
        //说明全部在右孩子
        ans += QueryTree(rt << 1 | 1, l, r);
    }
    else {
        ans += QueryTree(rt << 1, l, mid);
        ans += QueryTree(rt << 1 | 1, mid + 1, r);
    }
    return ans;
}

int main() {
    int n, m;
    while (cin >> n >> m) {
        BuildTree(1, 1, n);
        while (m--) {
            string op;
            int a, b, c;
            cin >> op;
            if (op == "Q") {
                cin >> a >> b;
                cout << QueryTree(1, a, b) << endl;
            }
            else {
                cin >> a >> b >> c;
                UpDate(1, c, a, b);
            }
        }
    }
    return 0;
}

expand:

Line segment tree from entry to advanced (ultra-clear, easy to understand ) 257B%2522request%255Fid%2522%253A%2522167413366516800217086477%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&re quest_id=167413366516800217086477&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~ default-1-104274713-null-null.142%5Ev71%5Econtrol_1,201%5Ev4%5Eadd_ask&utm_term=%E7%BA%BF%E6%AE%B5%E6%A0%91&spm=1018.2226.3001.4187

Logical AND (&&), Logical OR (||), Bitwise AND (&), Bitwise OR (|), Bitwise XOR (^), Bitwise Inversion (~)_Aczy156's Blog-CSDN Blog_Press Bit same or https://blog.csdn.net/qq_43345204/article/details/92794251?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167418008616800192278342%2522%252 C%2522scm%2522%253A%252220140713.130102334..% 2522%257D&request_id=167418008616800192278342&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-92794251-null-null.142%5Ev71%5E control_1,201%5Ev4%5Eadd_ask&utm_term=%E6%8C %89%E4%BD%8D%E5%BC%82%E6%88%96&spm=1018.2226.3001.4187
Line segment tree advanced delay mark (~ detailed arrangement) interval modification_unique_pursuit's blog-CSDN bloghttps://pursuit.blog.csdn.net/article/details/107880933?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-2-107880933-blog-79921997.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-2-107880933-blog-79921997.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=5 

Guess you like

Origin blog.csdn.net/qq_62687015/article/details/128738074