线段树

概念:

线段树是一棵完美二叉树,所有叶子的深度相同,并且每个节点要么是叶子要么是有 2 个儿子的树,树上的每个节点都维护一个区间 (最底层的节点退化到维护点) 。根是维护整个区间的,每个节点维护的是父亲的区间二等分后的其中一个子区间。

由于其二叉树的结构,可以满足当有 n 个元素时,对区间的操作可以在 O(\log n) 的时间内完成。因此,对于一些给出连续数据的问题,线段树可以在 O(\log n) 内很快的实现修改和询问。

根据节点中维护的数据不同,线段树可以提供不同的功能。

定义:

线段树的定义一般用数组实现

  • origin数组 存储的是原始数据,也就是需要用来建树操作的那一段连续数据
  • tree数组 存储的就是树的信息了,也是树的结构体现。通过改变存储的值,实现不同功能
  • lazy数组 存储懒惰信息,在 pushdown的时候用到,向下更新

tree 和 lazy 数组的大小应该是 origin数组的 4 倍左右。lazy数组 只有当树有区间更新时才需要定义。

const int MAXN=1e5 + 10;
int origin[MAXN], tree[MAXN<<2], lazy[MAXN<<2];

建树:

这里有几个变量

  • p 表示当前节点编号。左节点编号是 p << 1,右节点编号是 p << 1 | 1。分别表示 2*p,2*p+1
  • l、r 是在 origin数组 的位置,分别表示区间的左右端点

l、r 表示当前的操作区间,初始的调用时一定是 1、n,在递归中会不断改变。build函数一般不用改。

void build(int p, int l, int r) {        
    if (l == r) {
        tree[p] = origin[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    pushup(p);
}

向上更新节点的函数。准确的说,线段值是在这里产生的,若要改线段树存储数据类型,最先改的是这个。

void pushup(int p) {
    tree[p]=tree[p << 1]+tree[p << 1 | 1];
}

向下更新节点的函数,用了懒惰的思想。 当且仅当有区间更新时才需要用到。点更新、查询都不需要。

void pushdown(int p, int l, int r) { 
    if (lazy[p]) {
        lazy[p << 1] += lazy[p];
        lazy[p << 1 | 1] += lazy[p];
        int mid = (l + r) >> 1;
        tree[p << 1] += lazy[p] * (mid - l + 1);//左右儿子结点均按照需要加的值总和更新结点信息
        tree[p << 1 | 1] += lazy[p] * (r - mid);
        lazy[p] = 0;
    }
}

更新:

  点更新。由于点是最底层的,不需要向下更新,所以不包含 pushdown函数。但若夹杂着区间更新,则要加上。

void update_node(int p, int l,int r, int q,int v) {
    if (l == r) {   //查询到点
        tree[p] += v;
        return;
    }
    int mid = (l + r) >> 1;
    //pushdown(p, l, r);        若既有点更新又有区间更新,需要这句话
    if (q > mid) update_node(p << 1 | 1, mid + 1, r, q, v);
    else update_node(p << 1, l, mid, q, v);
    pushup(p);
}

区间更新。传给子节点时用到了“懒惰”的思想 (lazy数组),需要用到 pushdown函数。

void update(int p, int l, int r, int ql, int qr, int v) {   //区间更新
    if (ql <= l && r <= qr) {       //当前区间在更新区间内
        tree[p] += v * (r - l + 1);
        lazy[p] += v;
        return;
    }
    int mid = (l + r) >> 1;
    pushdown(p, l, r);
    if (ql <= mid)  update(p << 1, l, mid, ql, qr, v);
    if (qr > mid)  update(p << 1 | 1, mid + 1, r, ql, qr, v);
    pushup(p);
}

查询:

查询的操作经常需要改写,可以实现五花八门的功能。修改时,第6、7行一般是不需要改的,最多在 if 的判断语句中加条件。

int query(int p, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr)  return tree[p];    //被包含在询问区域内的区间(有效的部分)
    int mid = (l + r) >> 1;
    //pushdown(p, l, r);        //有区间更新时才需要
    int temp = 0;
    if (qr > mid) temp += query(p << 1 | 1, mid + 1, r, ql, qr);    //分块切割出有效的部分(已忽略无效部分)
    if (ql <= mid) temp += query(p << 1, l, mid, ql, qr);
    return temp;
}

注意:调用时,若有多组测试用例,一定别忘了初始化 lazy数组

 

先总结这么多,有新的收获后添加。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值