线段树

Posted zzz-hhh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树相关的知识,希望对你有一定的参考价值。

一、中心思想

二分, 每查找一个区间或者修改一个区间的时候,一层一层往下二分,找到

区间的左端点和右端点是不是在应该修改的区间内,然后进行修改操作...

二、基本操作

1、建树

首先你要弄明白这棵线段树是干什么的,即这棵线段树的节点要存储哪些信息

如果你不知道线段树里要存储哪些信息,回答以下三个问题:

①对于要求的输出,需要维护哪些信息(线段树节点里要存什么)?
②这些信息是否可以直接得到?
③如果不能,需要维护哪些信息来间接获得?

然后我们要做的事情就是:

两件事:由上而下干什么? 由下而上干什么?
由上而下的过程——初始化
由下而上的过程——合并区间信息
从上往下跑一圈又必须要倒回去的算法——递归。

那么就是递归实现的喽。

要开四倍空间

来自沙雕网友的解答:

理论上是2n-1的空间,但是你递归建立的时候当前节点为r
,那么左右孩子分别是2r,2r+1,此时编译器并不知道递归已结束,
因为你的结束条件是在递归之前的,所以编译器会认为下标访问出错,也就是空间开小了,
应该再开大2倍。有时候可能你发现开2,3倍的空间也可以AC,那只是因为测试数据并没有那么大。

主要的思路就是递归加二分
看看代码吧(~~怀念那学长看着我打代码,强行改我码风的日子)

void build(ll l,ll r,ll rt) 
    t[rt].len = r - l + 1;
    if(l == r) 
        t[rt].sum = read();
        return;
    
    ll m = (l + r) >> 1;
    build(l, m, lson);
    build(m + 1, r ,rson);
    pushup(rt);

单点查询

知道二分与递归之后,单点查询那不就成了一个,二分求解的过程了嘛

```cpp
void ask(int k)

if(tree[k].l==tree[k].r) //当前结点的左右端点相等,那么说明是叶子节点,是最终答案

ans=tree[k].w;
return ;

int m=(tree[k].l+tree[k].r)/2; // 二分查找
if(x<=m) ask(k2);
else ask(k
2+1);

单点修改

与单点查询没有什么区别,就是加了修改操作
还有一个回溯的过程

void add(int k)

    if(tree[k].l==tree[k].r)//找到目标位置 
    
        tree[k].w+=y;
        return;
    
    int m=(tree[k].l+tree[k].r)/2;
    if(x<=m) add(k*2);
    else add(k*2+1);
    tree[k].w=tree[k*2].w+tree[k*2+1].w;//所有包含结点k的结点状态更新 

懒标记

因为线段树的时候,有一些节点虽然我们下放了值了,也加上了,但是我们并没有用到这个地方
那么我们可以现在查询到的地方打上一个lazy标记(称为懒标记),然后每次查到这里的时候
就将当前的区间维护的值,与lazy标记处理一下就好了,这样我们就没有必要再向下跑到叶子节点
然后在合并区间信息了,很麻烦,也很慢...

区间修改

当他区间修改的时候,无非就是三种情况

1、当你查到的区间与需要修改的区间这样时

like this
技术图片

因为中心思想是二分,那么我们可以求出当前查到的区间的mid然后与

需要修改的区间的l和r进行比较,然后继续递归。

2、当你需要修改的区间在你当前查询的区间中时

like this
技术图片

按照上边的思路,我们要取查询到的区间的两边段点,然后按照mid的情况更新

3、当你查询到的区间完全在需要修改的区间内时

like this
技术图片

因为完全在区间内了,所以直接修改就好了

看一下代码吧

void updata(ll L,ll R,ll c,ll l,ll r,ll rt)
    if(L <= l && r <= R)
        t[rt].sum += t[rt].len * c;
        t[rt].lazy += c;
        return ;
    
    pushdown(rt);
    ll m = (l + r) >> 1;
    if(L <= m) updata(L,R,c,l,m,lson);
    if(R > m) updata(L,R,c,m + 1,r,rson);
    pushup(rt);

4、区间查询

和上边的差不多,情况也是上边的三种情况,然后这样我们可以去一个ans然后累加就行了

ll query(ll L,ll R,ll l,ll r,ll rt)
    if(L <= l && r <= R) return t[rt].sum;
    pushdown(rt);
    ll m = (l + r) >> 1,ans=0;
    if(L <= m) ans+=query(L, R, l, m, lson);
    if(R > m) ans+=query(L, R, m + 1, r, rson);
    return ans;

最后合并总的代码(因为单点查询与单点修改可以仿照区间查询与区间修改上来我就不放了)

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
#include<vector>
#include<string>
#define ll long long
#define N 1000010
#define lson rt << 1
#define rson rt << 1 | 1

using namespace std;
ll n,m,rt;
struct node 
    ll sum,lazy,len;
 t[N << 2 | 1];
ll read() 
    ll s=0,f=0;
    char ch=getchar();
    while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
    while(isdigit(ch)) s=s*10+(ch^48),ch=getchar();
    return f?-s:s;

void build(ll l,ll r,ll rt);
void pushup(ll rt);
void pushdown(ll rt);
void updata(ll L,ll R,ll c,ll l,ll r,ll rt);
ll query(ll L,ll R,ll l,ll r,ll rt);

int main() 
    n = read(), m = read();
    build(1,n,1);
    for(ll i = 1, opt, l, r, k; i <= m; i++)
        opt = read(),l = read(),r = read();
        if(opt == 1)k = read();updata(l,r,k,1,n,1);
        else printf("%lld\\n",query(l,r,1,n,1));
    
    return 0;


void build(ll l,ll r,ll rt) 
    t[rt].len = r - l + 1;
    if(l == r) 
        t[rt].sum = read();
        return;
    
    ll m = (l + r) >> 1;
    build(l, m, lson);
    build(m + 1, r ,rson);
    pushup(rt);


void pushup(ll rt) 
    t[rt].sum = t[lson].sum + t[rson].sum;


void pushdown(ll rt) 
    if(t[rt].lazy) 
        t[lson].lazy += t[rt].lazy;
        t[rson].lazy += t[rt].lazy;
        t[lson].sum += t[lson].len * t[rt].lazy;
        t[rson].sum += t[rson].len * t[rt].lazy;
        t[rt].lazy = 0;
    


void updata(ll L,ll R,ll c,ll l,ll r,ll rt)
    if(L <= l && r <= R)
        t[rt].sum += t[rt].len * c;
        t[rt].lazy += c;
        return ;
    
    pushdown(rt);
    ll m = (l + r) >> 1;
    if(L <= m) updata(L,R,c,l,m,lson);
    if(R > m) updata(L,R,c,m + 1,r,rson);
    pushup(rt);


ll query(ll L,ll R,ll l,ll r,ll rt)
    if(L <= l && r <= R) return t[rt].sum;
    pushdown(rt);
    ll m = (l + r) >> 1,ans=0;
    if(L <= m) ans+=query(L, R, l, m, lson);
    if(R > m) ans+=query(L, R, m + 1, r, rson);
    return ans;

以上是关于线段树的主要内容,如果未能解决你的问题,请参考以下文章

一般线段树与权值线段树

详解权值线段树

权值线段树&&线段树合并

zkw线段树

权值线段树

#树# #线段树#