首页 > 编程语言 >线段树(C++)

线段树(C++)

时间:2024-03-11 22:49:09浏览次数:19  
标签:node int 线段 C++ build 区间 sum

线段树的本质就是树状数组,只不过线段树不再需要lowbit函数来定位对应数据的存储位置,取而代之的则是直接计算分叉结果位置。


node结构体

​ 通常而言,线段树所需要的存储空间约等于原数组的4倍。由于线段树需要存储区间的范围,所以我们需要自己定义一个新结构体来方便存储:

const int N = [题目数据上限];
struct node{
    int l, r;//当前节点的左右区间
    int sum;//当前节点的总和,这部分可以依据题目进行改变
}
node t[N*4];

build函数

​ 有了数据,那就得建树,也就是build函数的意义。build函数在建树过程中,会以递归的形式不停的访问左右区间,直到区间的大小为1,也就是抵达了数据最本身,这时候便可以开始往回传递值,层层传递回去,完成建树。

例如:

build函数结束时:        10
递归的上一层:          3   7
原始数据(递归的底层):1 2 3 4

那么在数组里,他看起来究竟是什么样的,这里有一份输出可以参考着理解:

数据:1 2 3 4
at node[1]={l:1, r:4, sum:10}     
at node[2]={l:1, r:2, sum:3}      
at node[3]={l:3, r:4, sum:7}      
at node[4]={l:1, r:1, sum:1}      
at node[5]={l:2, r:2, sum:2}      
at node[6]={l:3, r:3, sum:3}      
at node[7]={l:4, r:4, sum:4}  

这样看起来就方便理解许多了。

build函数代码如下:

void build(int u, int l, int r)
{
    if(l == r)
        t[u] = {l, r, w[r]};
    else
    {
        t[u] = {l,r};
        int mid = (l+r) >> 1;
        build(u<<1, l, mid);
        build(u<<1|1, mid+1, r);
        pushup(u);
    }
}

代码中的u<<1以及u<<1|1实际上对应的操作等同于u*2u*2+1,位运算能优化一些比较常数级的东西,有时候也还是不得不相信常数级玄学的,说不准优化个常数就AC了呢。

pushup函数

​ 将左右分支的结果汇总到上层分支(也就是函数传参的u)

void pushup(int u){
    t[u].sum = t[u<<1].sum + t[u<<1|1].sum;
}

query函数

​ 查询区间的结果。查询需要从根节点开始一层一层向下查询,所以第一次调用的时候需要从u=1开始。查询过程为:如果当前区间已经满足了查询要求(也就是说当前的区间已经包含查询范围内,不需要完全等于),则可以返回sum值。如果当前区间范围太大,则分别向左半边区间和右半边区间查询,直到拿到查询区间内的所有信息。

int query(int u, int l, int r)
{
    if(t[u].l >= l && t[u].r <= r)
        return t[u].sum;
    int mid = (t[u].l + t[u].r) >> 1;
    int sum = 0;
    if(l<=mid)
        sum += query(u<<1, l, r);
    if(r > mid)
        sum += query(u<<1|1, l, r);
    return sum;
}

modify函数

​ 修改区间。仍然还是一样从根节点u=1开始向下遍历,这回我们需要找到对应数字,再进行修改,修改完成后再次进行pushup

void modify(int u, int x, int v)
{
    if(t[u].l == t[u].r)
        t[u].sum += v;
    else
    {
        int mid = t[u].l + t[u].r >> 1;
        if(x <= mid)
            modify(u<<1, x, v);
        else
            modify(u<<1|1, x, v);
        pushup(u);
    }
}

汇总以上代码便可得到另一个class:

class SegmentTree
{
private:
    struct node{
        int l,r;
        int sum;
    };
    int size;
    static const int N=100010;
    node t[N*4];
    int w[N];


public:
    void getArray(vector<int> nu)
    {
        for(int i : nu)
        {
            size++;
            w[size] = i;
        }
        build(1, 1, size);
    }
    void build(int u, int l, int r)
    {
        if(l == r)
            t[u] = {l, r, w[r]};
        else
        {
            t[u] = {l,r};
            int mid = (l+r) >> 1;
            build(u<<1, l, mid);
            build(u<<1|1, mid+1, r);
            pushup(u);
        }
    }
    void pushup(int u)
    {
        t[u].sum = t[u<<1].sum + t[u<<1|1].sum;
    }
    int query(int u, int l, int r)
    {
        if(t[u].l >= l && t[u].r <= r)
            return t[u].sum;
        int mid = (t[u].l + t[u].r) >> 1;
        int sum = 0;
        if(l<=mid)
            sum += query(u<<1, l, r);
        if(r > mid)
            sum += query(u<<1|1, l, r);
        return sum;
    }
    void modify(int u, int x, int v)
    {
        if(t[u].l == t[u].r)
            t[u].sum += v;
        else
        {
            int mid = t[u].l + t[u].r >> 1;
            if(x <= mid)
                modify(u<<1, x, v);
            else
                modify(u<<1|1, x, v);
            pushup(u);
        }
    }
};

树状数组和线段树的Snippet会在下一篇中放出。

标签:node,int,线段,C++,build,区间,sum
From: https://www.cnblogs.com/ComputerEngine/p/18067262

相关文章

  • (C++)树状数组和线段树的VSCode Snippet
    学都学了,肯定要往snippet里塞好东西嘛{ //Placeyoursnippetsforcpphere.Eachsnippetisdefinedunderasnippetnameandhasaprefix,bodyand //description.Theprefixiswhatisusedtotriggerthesnippetandthebodywillbeexpandedandinserted.......
  • 用c++实现净现值的计算
    我是用c++实现的,我是把贴现率保留了四位小数。下面是我写的代码:#include<iostream>#include<cmath>usingnamespacestd;floatjst(intj,floatm,floatlv){while(j!=0){m*=(1+lv);j--;}return1.0/m;}i......
  • C++ 津津的储蓄计划
    描述津津的零花钱一直都是自己管理。每个月的月初妈妈给津津300元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同。为了让津津学习如何储蓄,妈妈提出,津津可以随时把整百的钱存在她那里,到了年末她会加上20%还给津津。因此津津制定了一个储蓄计划:每个月的月初,在得到妈妈......
  • C++ 雇佣兵
    描述雇佣兵的体力最大值为M,初始体力值为0、战斗力为N、拥有X个能量元素。当雇佣兵的体力值恰好为M时,才可以参加一个为期M天的战斗期,战斗期结束体力值将为0。在同一个战斗期内,雇佣兵每连续战斗n天,战斗力就会上升1点,n为当前战斗期开始时的战斗力。一个战斗期结束后,雇佣兵需要用若......
  • [3] C++面向对象编程
    Day1函数指针数组简写函数指针typedeftypedefint(*FunPtr)(int,int);FunPtrFunArr[1]={Add};内联函数#pragmaregion内联函数//避免函数跳转对于程序的额外开销//有两种写法1).h中写实现文件(在.h中同时写声明和实现)//2)inline关键字......
  • C++学习笔记
    第一章认识C++1.1命名空间1.1.1命名空间的基本格式命名空间是一个由用户自己定义的作用域,在不同作用域中定义相同变量,不会冲突。命名空间中可以存放以下类型,这些定义/声明在结构体中的内容成为实体变量常量函数(可以是定义或声明)结构体类模板命名空间(可以嵌套定义)......
  • error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft
       Defaultingtouserinstallationbecausenormalsite-packagesisnotwriteableCollectingPyQt5-sipUsingcachedPyQt5_sip-12.13.0.tar.gz(123kB)Installingbuilddependencies...doneGettingrequirementstobuildwheel...donePreparing......
  • 线段树 学习笔记
    简介略。-1一个模板改编自tourist。其中只需要写apply、pushdown、mergeclasssegtree{public:structnode{//don'tforgettosetdefaultvalue(usedforleaves)//notnecessarilyneutralelement!intsum=0,add=0;voi......
  • 线段树学习笔记(更新中)
    本文章开始写作于2024年3月10日22:48,这也是我第一次没有参考板子,独立写出一个线段树的时刻(虽然只是板子题并且debug的时候还是参考了一下)写这个主要是为了我自己以后复习起来方便,毕竟这玩意还是极其容易写挂的注意:以下内容中标为斜体的是需要按照题目要求具体情况具体分析的,文章......
  • 函数回调(C++)
    函数回调C++部分​ 从C#逆向理解回去,这玩意应该就是delegate的原型了,只不过C#中将其作为一个单独的变量类型方便做管理,而C++这个老毕登这里则是以指针的形式表现出来。​ 作用在于,你不需要关心函数具体内容是什么,也不需要关心函数到底会处理什么,你只需要直接调用这个定义了的回......