延迟标记——线段树进阶

延迟标记理解起来是很好理解,但是用代码实现却对我来说有些困难,可能是递归,优化,以及区间操作的原因,弄得我现在有点昏昏的,写一个博客,整理一下思路

根据题目可以以下的准备工作

这是第一个学习版本,求和以及sum延迟标记add都放在了结构体外面,使用了宏定义来简便代码的书写,数组开的大小依据题目,练习题目是POJ 3468     

A Simple Problem with Integers

 可以说是模板题

#define lson l,m,rt<<1
#define rson m + 1,r,rt<<1|1
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll sum[N << 2];
ll add[N << 2];
struct Node{
    int l,r;
    int mid()
    {
        return (l + r) >> 1;
    }
}tree[N << 2];
不管怎么样,仍然是线段树,还是要建树的~~
void build(int l,int r,int rt)
{
    tree[rt].l = l;
    tree[rt].r = r;
    add[rt] = 0;
    if(l == r)
    {
        scanf("%I64d",&sum[rt]);
        return;
    }
    int m = tree[rt].mid();
    build(lson);
    build(rson);
    Pushup(rt);
}
只不过建树的过程中

1.初始化延迟标记

2.输入叶子数据

3.递归返回时进行树干的更新维护,直到结束

PS:在这里更新维护放在了if的外面这是正确的,先来看一下向上更新的代码

void Pushup(int rt)
{
    sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}
传入的是一个父节点,如果放在了if里面就会更新失败,其实看一看if里面的语句实际上就是一个特殊的针对叶子的更新,输入的值直接赋给了sum【rt】,这也就为返回时对父节点的更新做了基础

接下来,就是比较困难的延迟标记了,延迟标记是针对区间变化而产生的

void update(int c,int l,int r,int rt)
{
    //c是要传入的lazy值
    if(tree[rt].l == l &&tree[rt].r == r)
    {
        //区间重合的时候进行的操作
        add[rt] += c;
        sum[rt] += (ll)c*(r - l + 1);
        return;
    }
    Pushdown(rt,tree[rt].r - tree[rt].l + 1);
    //查询的时候,进行add的更新操作,不一定针对于本次的add更新,可能是上一次的

    int m = tree[rt].mid();
    if(r <= m)update(c,l,r,rt<<1);
    else if(l > m)update(c,l,r,rt<<1|1);
    else
    {
        update(c,l,m,rt<<1);
        update(c,m + 1,r,rt<<1|1);
    }
    Pushup(rt);
}
c是要传入的lazy值,也就是针对这一个区间要进行的整体的值变化操作

延迟标记之所以快,是因为update时,并不将更新进行到底(叶子)而是先标记一下,因为我们要进行的工作还是query()

询问,如果询问的时候需要那个值,我们才会顺便(是顺便!!大大减少了时间)更新一下那个区间的值,并取消延迟标记

所以当子区间重合时,标记上(更新add)延迟标记,更新一下该节点的sum就直接返回了

然后我们在更新时也可以顺便检查一下,我们寻找的更新区间有没有lazy标记,并尝试做一下向下更新,这个更新几乎不耗时间,具体看一下函数pushdowm的代码,时基于本层的操作,用到了该层,就更新,然后将lazy标记传到下一层就先不管了,所以真的是lazy啊~~~

void Pushdown(int rt,int m)
{
    //m是父节点的区间长度
    if(add[rt])
    {
        add[rt<<1] += add[rt];
        add[rt<<1|1] += add[rt];
        sum[rt<<1] += add[rt] * (m - (m >> 1));
        sum[rt<<1|1] += add[rt] * (m >> 1);
        add[rt] = 0;
    }
}
PS    :   rt<<1|1就是左移一位(*2)再或1(位运算逻辑或操作,有1得一,与零异或值不变,所以相当于最后一位添1,其它二进制位值不变)+ 1

之后递归对区间进行标记,返回的时候要更新sum得值

然后就可以开始询问了

ll query(int l,int r,int rt)
{
    if(l == tree[rt].l && r == tree[rt].r)return sum[rt];
    Pushdown(rt,tree[rt].r - tree[rt].l + 1);
    int m = tree[rt].mid();
    ll res = 0;
    if(r <= m)res += query(l,r,rt<<1);
    else if(l > m)res += query(l,r,rt<<1|1);
    else
    {
        res += query(l,m,rt<<1);
        res += query(m + 1,r,rt<<1|1);
    }
    return res;

}
区间重合得时候返回该区间的sum值

否则先检查有无可更新的lazy标记,然后递归求和,非常容易了~~~


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值