线段树

线段树

线段树是一种二叉搜索树

==========================

储存方式

struct node{
    int l, r;  //当前节点的区间范围
    int v;     //其他需要储存的信息
} tr[N];  

需要储存的信息,需要看目前的信息,父节点可否由子节点信息直接得到,若不能需要增加新的储存信息(具体见例题2,3

 u点的左儿子 2u   u << 1 即 tr[u << 1]
 u点的右儿子 2u+1  u << 1 | 1 即 tr[u << 1 | 1]

五大基本操作

 1.pushup   通过子节点信息,计算父节点信息 - logn
 2.build    将一段区间初始化成线段树(建立)
 3.modify   修改某个点或某个区间
 4.query   查询某一段区间
 5.pushdown 将父节点的修改传到子节点(见例题3,4(两个懒标记)


单点修改,区间查询
区间修改,区间查询
区间修改,单点查询

===========================


0.pushup

通过子区间信息更新父区间信息
一般放在build函数和modify函数的最后
eg:更新父区间的和

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

1.build

传入当前节点编号u,左端点l,右端点r

void build(int u, int l, int r){
    tr[u].l = l;    存当前节点的左编号
    tr[u].r = r;    存当前节点的右编号
    if ( l == r ) return;   判断是否是叶子节点
    int mid = l + r >> 1;   当前区间得中点
    build(u << 1, l, mid);  递归到左儿子
    build(u << 1 | 1, mid + 1, r);  递归到右儿子
    (一般在此处写pushup操作)
}

2.query

假设当前查询的区间为[l,r] 查询的节点范围为[tl,tr]
 1.[l,r]包含在[tl,tr]区间内
   直接返回
 2.[l,r]不包含在[tl,tr]但是有交集(每次递归后判断是情况1还是2)
   1.和左边有交集 -> 递归到左边
   2.和右边有交集 -> 递归到右边
   3.左边和右边都有交集 -> 两边都递归**(对于每次访问,只会执行一次)**
访问的时间复杂度在logn内(最多4logn)

eg:找当前区间的最大值

int query(int u, int l, int r) {
    int v = 0;      //最大值
    if ( tr[u].l >= l && tr[u].r <= r ) return tr[u].v; //若访问的区间在要求区间内
    else{
        int mid = tr[u].l + tr[u].r >> 1;
        if ( mid >= l ) v = query(u << 1, l, r);    //中点在l右边则递归左儿子
        if ( mid < r ) v = max(v, query(u << 1 | 1 ,l ,r)); //不取等,因为右儿子是从mid+1开始
    }
    return v;
}

3.modify

假设当前访问的区间为[l,r] 修改的区间范围为[tl,tr]
 1.若tr[l,r]是叶节点
   直接修改
 2.若要修改的区间的中点在l右边(大于或等于)
   递归左儿子
 3.若要修改的区间的中点在r左边(小于,建树时右区间从mid+1开始,不取等于)
   递归右儿子
修改完回溯时要记得用pushup函数更新父节点信息

eg:给x处加上v

void modify(int u, int x, int v){
    if ( tr[u].l == tr[u].r ) tr[u].v = v;  //若是叶节点则赋值
    else{
        int mid = tr[u].r + tr[u].l >> 1;
        if ( mid >= x ) modify(u << 1, x, v);   //同上,区间变成了点
        else modify(u << 1 | 1, x, v);
        pushup(u);      //回溯时通过子节点,更新父节点的最大值
    }
}

4.pushdown

实现区间修改,区间查询
时间复杂度o(logn)
作用:将父节点的修改传到子节点
若不用懒标记,区间修改的时间复杂度最坏o(n),过慢

pushdown一般放在modify和query前面
add懒标记
-> 在当前节点为跟的子树中的每一个节点加上add,不包含当前区间自己(看个人习惯)
从上往下递归时,将add标记传到子区间,并删去当前节点的标记 -> pushdown
eg:
跟节点为root,左子区间为left,右子区间为right,pushdown操作如下

    left.add += root.add	//将父区间懒标记add传入左子区间
    left.sum += (left.r - left.l + 1) * root.add //更新左子区间的信息(这里是总和的例子)
    right.add += root.add	//将父区间的懒标记add传入右子区间
    right.sum += (right.r - right.l + 1) * root.add //更新右子区间的信息(这里是总和的例子)
    root.add = 0; 	//删去父区间的懒标记

在修改或询问操作,向左右子区间分裂时

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值