首页 > 其他分享 >线段树杂谈

线段树杂谈

时间:2024-08-15 10:27:21浏览次数:11  
标签:rs int res 线段 杂谈 mid 节点

动态开点线段树

引入

普通的线段树写法有一个显然的缺点——空间。堆式存贮使得我们开线段树时需要用 $ 4n $ 的空间。冗余空间高达 $ 2n $ 。而且,在大多数情况下线段树中并不是每个节点都会被用到,这时我们就可以使用动态开点线段树,不仅所用的空间小,实现起来的代码也比普通线段树短。

思想

动态开点线段树,顾名思义,就是一棵使用动态开点的线段树。(废话

在常用的堆式存储中,我们用 $ p \times 2 $ 和 $ p \times 2 + 1 $ 来表示节点 $ p $ 的两个儿子。而在动态开点线段树中,我们用 $ lson $ 和 $ rson $ 两个数组来记录每个节点的两个儿子的编号。并且 节点只有在被用到的时候才创建。这样,我们就能有效避免冗余空间的出现。

在修改时,如果我们发现当前节点并没有在线段树上,那么我们就创建这个节点。

模板(区间修改)
void modify(int &p, int L, int R, int l, int r, int c) {//L,R表示当前节点所包含的区间,l, r表示修改区间
    //注意到是&p,这样可以使得上一层递归中不用再次手动修改传入的节点
    if(!p) p = ++tot;//创建新节点
    if(l <= L && R <= r) {
        ······//修改
        return ;
    }
    pushdown(p, L, R);
    int mid = L + R >> 1;
    if(l <= mid) modify(t[p].ls, L, mid, l, r, c);
    if(r > mid) modify(t[p].rs, mid + 1, R, l, r, c);
    pushup(p);
}

在询问时,如果当前节点并未被创建,那么就可以返回 $ 0 $ 。这是因为如果当前节点没有被创建,说明这个区间没有被修改过,换句话说,这个区间所维护的东西不存在,即为 $ 0 $。

模板(区间查询)
int query(int p, int L, int R, int l, int r) {
    if(!p) return 0;
    if(l <= L && R <= r) return ···;//返回区间所维护的东西
    pushdown(p, L, R);
    int mid = L + R >> 1, res = 0;
    if(l <= mid) ···; //查询并更新
    if(r > mid) ···;
    return res;
}

如果线段树上有初值的话,我们可以将其看做若干个修改。这样就不用写 $ build $ 函数了。

例题

$ \color {skyblue} P3372 【模板】线段树 1 $

题意

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 $ k $。
  2. 求出某区间每一个数的和。
思路

线段树维护

代码
#include<bits/stdc++.h>
#define int long long
#define Debug puts("Oops!")
using namespace std;

const int N = 1e5 + 5, M = 5e5 + 5;

int n, m;

struct Node {
    int ls, rs;
    int lazy, dat;
};

struct Segment_Tree {
    int root, tot;
    Node t[N << 1];
    void pushup(int p) {t[p].dat = t[t[p].ls].dat + t[t[p].rs].dat;}
    void pushdown(int p, int l, int r) {
        if(!t[p].ls) t[p].ls = ++tot;
        if(!t[p].rs) t[p].rs = ++tot;
        t[t[p].ls].lazy += t[p].lazy;
        t[t[p].rs].lazy += t[p].lazy;
        int mid = l + r >> 1;
        t[t[p].ls].dat += t[p].lazy * (mid - l + 1);
        t[t[p].rs].dat += t[p].lazy * (r - mid);
        t[p].lazy = 0;
    }
    void add(int &p, int L, int R, int l, int r, int c) {
        if(!p) p = ++tot;
        if(l <= L && R <= r) {
            t[p].dat += c * (R - L + 1);
            t[p].lazy += c;
            return ;
        }
        pushdown(p, L, R);
        int mid = L + R >> 1;
        if(l <= mid) add(t[p].ls, L, mid, l, r, c);
        if(r > mid) add(t[p].rs, mid + 1, R, l, r, c);
        pushup(p);
    }
    int query(int p, int L, int R, int l, int r) {
        if(!p) return 0;
        if(l <= L && R <= r) return t[p].dat;
        pushdown(p, L, R);
        int mid = L + R >> 1, res = 0;
        if(l <= mid) res += query(t[p].ls, L, mid, l, r);
        if(r > mid) res += query(t[p].rs, mid + 1, R, l, r);
        return res;
    }
}st;

inline int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)) {if(c == '-') f = -1; c = getchar();}
	while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
	return x * f;
}

signed main() {
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
    n = read(), m = read();
    for(int i = 1, x; i <= n; i++) {
        x = read();
        st.add(st.root, 1, n, i, i, x);
    }
    while(m--) {
        int op = read(), l =  read(), r = read();
        if(op == 1) {
            int x = read();
            st.add(st.root, 1, n, l, r, x);
        }
        else {
            cout << st.query(st.root, 1, n, l, r) << endl;
        }
    }
	return 0;
}

标签:rs,int,res,线段,杂谈,mid,节点
From: https://www.cnblogs.com/zeta-y/p/18360387

相关文章

  • 线段树+懒标记 (区间修改,区间查询)
    原作者:董晓P3372【模板】线段树1//结构体版#include<cstring>#include<iostream>#include<algorithm>usingnamespacestd;#defineN100005#defineLLlonglong#definelcu<<1#definercu<<1|1LLw[N];structTree{//线段树LLl,r,......
  • 李超线段树
    用途:用于二维坐标系维护多条线段。算法:本质上是采用标记永久化,对每个线段树节点维护一个标记表示该区间存在这一条线段,查询时从上到下经过节点的标记即为该横坐标上可能经过的线段。下面需在标记(线段)间的比较上作考虑:建议画图理解此时对于一个区间\([l,r]\),找出中点\(mid......
  • unity中, 二维平面上,求从点A出发,沿着方向B,与线段C的交点
    代码说明:点A:起始点。方向B:一个方向向量,表示从点A出发的方向。线段C:由两个点C1和C2定义。1usingUnityEngine;23publicclassLineIntersection:MonoBehaviour4{5//返回从点A出发,沿着方向B,与线段C的交点。如果没有交点,则返回null6publicstati......
  • 可持久化线段————主席树(洛谷p3834)
    洛谷P3834可持久化线段树2问题描述:给定n各整数构成的序列,求指定区间[L,R]内的第k小值(求升序排序后从左往右数第k个整数的数值)输入:第一行输入两个整数n,m,分别代表序列长度n和对序列的m次查询;第二行输入n个整数,表示序列的n个整数;之后的m行,每行输入3个整数L,R,k,表示查询......
  • 线段树
    模板记得开4倍空间!!!Code#include<bits/stdc++.h>#definelllonglong#definepfprintf#definesfscanfusingnamespacestd;constintN=1e5+7;inttr[N*4];intf[N*4];inta[N];intn,q;intl,r,val;voidbuild(intu,intl,intr){ if(l==r){ tr[u]=a......
  • 【笔记】吉如一线段树
    【笔记】吉如一线段树吉如一论文(CQBZ内网,在PDF的103页1区间最值操作1.1区间取min(max),区间和当前应该修改值为\(x\);维护区间最大值\(mx\),最大值个数\(t\),严格次大值\(se\)。如果走到一个区间上,如果:\(x\gemx\),说明取min操作没用,直接return;\(mx>x>se\),打标......
  • 李超线段树板子
    点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=1e6+5;constintmod=1e9;constintp=39989;constdoubleeps=1e-9;intn,m;intans,ansid;inttcnt,rt;structlmy{ doublek,b;}a[N];structTree{ intls,rs,id;}tr[N];boolc......
  • 【笔记】传统势能线段树
    1引入传统线段树能够通过打标记实现区间修改的条件有两个:能够快速处理标记对区间询问结果的影响;能够快速实现标记的合并。有的区间修改不满足上面两个条件。但存在一些奇妙的性质,使得序列每个元素被修改的次数有一个上限。如果我们保证每暴力\(O(\logn)\)修改一次的时......
  • 线段树进阶 Part 1
    线段树是信息学竞赛最常见的数据结构。本篇笔记总结技巧和应用,不介绍基本线段树算法。1.常见技巧1.1信息设计用线段树解决问题,首先得考虑维护哪些信息。若不带修,任何满足结合律且封闭的信息(称为半群)都是可维护的。结合律一般都有,封闭性帮助我们设计信息。例如区间最大子段......
  • 「Day 7—离散化 & 树状数组 & 线段树」
    离散化定义离散化本质是一种哈希,是一种用来处理数据的方法。1.创建原数组的副本。2.将副本中的值从小到大排序。3.将排序好的副本去重。4.查找原数组的每一个元素在副本中的位置,位置即为排名,将其作为离散化后的值。B3694数列离散化代码#include<iostream>#include<algo......