线段树详解

线段树详解

1.引例

假如现在有一个问题,让你修改一段区间的数,并询问任意一段区间的和,你该怎么办?

方法一:暴力修改加查询---------------不可以,时间复杂度太高了

方法二:利用前缀和查询---------------也不可以,修改怎么办呢?

方法三:线段树维护区间---------------可以~~~

2.前置知识

线段树

3.基本思路

线段树就是把树转换成一个区间,并用二分的思想层层递归下去,直到当前的信息能够返回答案(或已经递归到了叶节点)

而线段树能够解决的问题主要是单点查询,单点修改,区间查询,区间修改

下面演示一下线段树具体怎么操作

如果有一个区间[1-10]

那么第一次二分的区间即为[1-5][6-10],不能返回值,继续递归

第二次:[1-3][4-5],[6-8][9-10],不能返回值,继续递归

第三次:[1-2],[3],[4],[5],[6-7],[8],[9],[10],现在区间3,4,8,9,10都可以返回

第四次:[1],[2],[6],[7],全部可以返回

以上就是线段树的操作过程

4.代码实现

让我们先了解线段树的基本操作

1.建树(build)

2.上传(pushup)

3.下传(pushdown)

4.修改(modify)

5.查询(query)

这就是线段树的五种基本操作

(1)建树(build)

建树就是把一棵树转换成一段区间,还是运用了二分思想(不要问我为什么,线段树很玄学)

当递归到了叶结点的话,把已知的信息存进树(结构体)里 否则,继续二分

以下就是建树的代码

void build(int u,int l,int r)
{
    if(l==r)tr[u]={l,r,a[r]};
    else
    {
        tr[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表示的是当前节点的子节点,当然用2u,和2u+1也是没问题的

(2)上传(pushup)

上传很好理解,就是把子节点的信息上传到父节点

同时,上传操作不是固定的,它因你的操作而改变

比如说,你想求区间和,代码就是tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;

区间最大值就是tr[u].Max=max(tr[u<<1].Max,tr[u<<1|1].Max);

同理,最小值就是tr[u].Min=min(tr[u<<1].Min,tr[u<<1|1].Min);

(3)下传(pushdown)

下传和上传正好相反,下传是把信息传到子结点上

同理,下传也是不固定的

在这里演示一下区间和的代码

void pushdown(int u)
{
    auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add)
    {
        left.add+=root.add,left.sum+=(ll)(left.r-left.l+1)*root.add;
        right.add+=root.add,right.sum+=(ll)(right.r-right.l+1)*root.add;
        root.add=0;
    }
}

这里的sum是从L到R区间的和,所以要乘上父节点的add

不要忘记最后把root.add还原成0

(4)修改(modify)

修改的操作分两种情况

①当前可以返回值----------直接返回

②不可以返回值 -------------继续递归

于是我们的代码就成了:

void modify(int u,int l,int r,int d)
{
    if(tr[u].l>=l&&tr[u].r<=r)
    {
        tr[u].add+=d;
        tr[u].sum+=(ll)(tr[u].r-tr[u].l+1)*d;
    }
    else
    {
        pushdown(u);
        int mid=tr[u].l+tr[u].r>>1;
        if(l<=mid)modify(u<<1,l,r,d);
        if(r>mid)modify(u<<1|1,l,r,d);
        pushup(u);
    }
}

(5)查询(query)

明白了修改,理解查询操作就很简单了 也是两种情况

①当前可以返回结果----------直接返回

②不可以返回结果--------------分成两段来求解

下面给的代码以求区间和为例

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

4.实践

学会了线段树之后,需要多刷题来巩固

这里推荐几道题

1.【模板】线段树 1

2.统计和

3.【模板】ST 表

最后一个温馨提示:

十年oi一场空,不开longlong见祖宗

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值