线段树(2)

lazy

(记录下对懒标记的认识)
lazytage主要用于“区间修改”的优化中,可使算法的时间复杂度到O(logn)
lazytage目的在于在修改区间的过程中不是每次都直接修改到叶子结点,而是在 l <= pl <= pr <= r 的情况下直接返回,在回溯之前向节点p增加一个标记(表明当前结点已经修改,但子结点未被更新“。

懒标记要满足一个性质叫可叠加性,就是说你可以通过上面的来算出下面两个儿子的值,也可以反推回去。

ps. 凡是要分裂的,就需要往下传(pushdown)

example

  1. 加法线段树

A Simple Problem with Integers (POJ3468)

C l r d
将数列中第l~r个数都加d
Q l r
询问数列第l~r个数的和

加法的懒标记:

struct Node{
    int l,r;
    ll sum,add;
    // sum 当前区间总和
    // add 懒标记
}tr[N*4];
void pushdown(int u){
    auto &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
    if(root.add){
        left.add += root.add;
        right.add += root.add;
        left.sum += (ll)(left.r-left.l+1)*root.add;
        right.sum += (ll)(right.r-right.l+1)*root.add;
        root.add = 0;
    }
}
void modify(int u, int l, int r, int d)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].sum += (ll)(tr[u].r - tr[u].l + 1) * d;
        tr[u].add += d;
    }
    else    // 一定要分裂
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, d);
        if (r > mid) modify(u << 1 | 1, l, r, d);
        pushup(u);
    }
}

modify:
else{ } 是由于要分裂,分裂左右子树的标记可能不同,所以要先pushdown

区间和 sum = ( r - l + 1) * lazytape

Q:为啥pushdown中对左右子树sum直接操作
A:因为可以发现,在modify中,只有 if (tr[u].l >= l && tr[u].r <= r) 即区间完全覆盖的情况下才会将懒标记tr[u].add += d; 所以在pushdown中可以直接对左右子树sum操作(只有先完全覆盖了,才会有lazytype!)

ll query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    ll sum = 0;
    if (l <= mid) sum = query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

query中除了区间完全覆盖,一定也要记得将父结点的lazytape给pushdown

  1. 乘法线段树

如果这个线段树只有乘法,那么直接加入lazytage变成乘,然后 tr[u].sum *= k 就好了。

  1. 加法&乘法线段树

此类lazytage分为两种,分别是加法的plz和乘法的mlz。
mlz很简单处理,pushdown时直接乘父亲的就可以了,对于加法的操作:
我们需要把原先的plz*父亲的mlz再加上父亲的plz.(先乘后加

AcWing 1277.维护序列

操作 1:1 t g c,表示把所有满足 t≤i≤g 的 ai 改为 ai×c;
操作 2:2 t g c,表示把所有满足 t≤i≤g 的 ai 改为 ai+c;
操作 3:3 t g,询问所有满足 t≤i≤g 的 ai 的和 模 P 的值。

在这里插入图片描述

ps.
懒标记要满足可叠加性,可以通过上面的来算出下面两个儿子的值,也可以反推回去,本题先加再乘不行的原因就是不满足可叠加性。

让乘法优先级更高,因为如果先加会出现一些分数,分数带来的严重问题就是精度的缺失。所以这道题需要先乘后加。

struct Node
{
    int l, r;
    int add, sum, malt;//sum表示区间和
  					   //add和malt是懒标记
}tr[N * 4];
//懒标记和这个维护的sum要有可叠加性,所以选择了先乘再加
void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    tr[u].sum = tr[u].sum % p;
}
void pushdown(int u)
{
//先乘再加,然后取模,注意longlong
    tr[u << 1].sum = ((ll)tr[u << 1].sum * tr[u].malt + (ll)tr[u].add * (tr[u << 1].r - tr[u << 1].l + 1)) % p;
    tr[u << 1 | 1].sum = ((ll)tr[u << 1 | 1].sum * tr[u].malt + (ll) tr[u].add * (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)) % p;
    tr[u << 1].add = ((ll)tr[u << 1].add * tr[u].malt + tr[u].add) % p;
    tr[u << 1 | 1].add = ((ll)tr[u << 1 | 1].add * tr[u].malt + tr[u].add) % p;
    tr[u << 1].malt = (ll)tr[u << 1].malt * tr[u].malt % p;
    tr[u << 1 | 1].malt = (ll)tr[u << 1 | 1].malt * tr[u].malt % p;
    tr[u].add = 0;//最后记得清空懒标记
    tr[u].malt = 1;//乘法的懒标记是1
}
作者:cht
链接:https://www.acwing.com/solution/content/34191/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

bulid 注意要pushup(u);
记得上传,build是一层层往下的,不用分裂处理,也就是不用pushdown

void modify(int u, int l, int r, int malt, int add){
    if(tr[u].l >= l && tr[u].r <= r)//完全在区间内部
    {
        //改变懒标记和sum,先乘后加,注意开long long
        tr[u].sum = ((ll)tr[u].sum * malt + (ll)add * (tr[u].r - tr[u].l + 1)) % p;
        tr[u].add = ((ll)tr[u].add * malt + add) % p;
        tr[u].malt = (ll)tr[u].malt * malt % p;
    }
    else//分裂处理,先pushdown
    {
        pushdown(u);//处理懒标记
        int mid = (tr[u].l + tr[u].r) >> 1;//算一下终点
        if(l <= mid) change(u << 1, l, r, malt, add);//算左右两边
        if(r > mid) change(u << 1 | 1, l, r, malt, add);
        pushup(u);//合并一下,然后处理sum
    }
}
int query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r)	return tr[u].sum;		//如果在区间之内直接返回
  	pushdown(u);
    int mid = (tr[u].l + tr[u].r) >> 1;
    ll res = 0;
    if(l <= mid)	res = query(u << 1, l, r);
	if(r > mid)     res += query(u << 1 | 1, l, r)% p;
    return res;
}
  1. 根号线段树(copy)

根号线段树和除法线段树一样,但如果直接用lazytage标记除了多少,实际上,会出现精度问题。

c++的除法是向下取整,很明显,(a+b)/k!=a/k+b/k(在向下取整的情况下),而根号,很明显根号(a)+根号(b)!=根号(a+b)。

我们对于每个区间,维护其的最大值和最小值,然后每次修改时,如果这个区间的最大值根号和最小值的根号一样,说明这个区间整体根号不会产生误差,就直接修改(除法同理)

其中,lazytage把除法当成减法,记录的是这个区间里每个元素减去的值。

pushdown没什么变化,就是要记得tree[i].minn、tree[i].maxx也要记得-lazytage。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值