lazy
(记录下对懒标记的认识)
lazytage主要用于“区间修改”的优化中,可使算法的时间复杂度到O(logn)
lazytage目的在于在修改区间的过程中不是每次都直接修改到叶子结点,而是在 l <= pl <= pr <= r 的情况下直接返回,在回溯之前向节点p增加一个标记(表明当前结点已经修改,但子结点未被更新“。
懒标记要满足一个性质叫可叠加性,就是说你可以通过上面的来算出下面两个儿子的值,也可以反推回去。
ps. 凡是要分裂的,就需要往下传(pushdown)
example
- 加法线段树
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
- 乘法线段树
如果这个线段树只有乘法,那么直接加入lazytage变成乘,然后 tr[u].sum *= k 就好了。
- 加法&乘法线段树
此类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;
}
- 根号线段树(copy)
根号线段树和除法线段树一样,但如果直接用lazytage标记除了多少,实际上,会出现精度问题。
c++的除法是向下取整,很明显,(a+b)/k!=a/k+b/k(在向下取整的情况下),而根号,很明显根号(a)+根号(b)!=根号(a+b)。
我们对于每个区间,维护其的最大值和最小值,然后每次修改时,如果这个区间的最大值根号和最小值的根号一样,说明这个区间整体根号不会产生误差,就直接修改(除法同理)
其中,lazytage把除法当成减法,记录的是这个区间里每个元素减去的值。
pushdown没什么变化,就是要记得tree[i].minn、tree[i].maxx也要记得-lazytage。