您的位置:首页 > 其它

延迟标记——线段树进阶

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

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

这是第一个学习版本,求和以及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标记,然后递归求和,非常容易了~~~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: