多lazytag线段树

本文详细分析了树状数组(区间查询与修改)中懒惰标记的处理策略,特别是先乘后加的顺序。讨论了为何选择先乘后加作为优先级,并通过实例解释如何将先加后乘转化为先乘后加,同时展示了如何在下传过程中维护节点的sumv和lazytag。最后,给出了相应的C++代码实现。
HDU4578

这是一道超好的题,麻烦归麻烦,加深了我对lazytag的认知,做了它我才知道原来之前在lazytag下传顺序这里我并没理解完全透彻顺序是怎么决定的。
lazytag下传的优先级本质上是可以任意的,但我们不该给自己制造难度,该选择最方便于我们计算的办法。其中核心思想是能不能用当前下传顺序比较容易且在不损失精度、尽量避免繁琐的情况下表示出其它下传顺序,下面我们就这道题来讲一下先乘后加的优先级是怎么分析出来的:
首先,只要有把一个区间变成一个数c的操作,不管这个结点此前打上多少别的lazytag,都是要作废的,即这一步会覆盖之前所有lazytag,故优先级最高。至于为什么应该不难理解,就是覆盖了。
而我们我们要思考的问题是有lazytag时,改查操作需要下传lazytag改变值时是先乘后加还是先加后乘呢?
①先乘后加:
我们定义lazytag下传顺序为先乘后加,如果一个元素所在区间有两个lazytag分别为add(加)、mul(乘)待下传至这个元素上,顺序如果是先乘后加则按照这个默认顺序下传改变元素值即可;但如果是先加后乘的改变呢?我们怎么将先加后乘的式子(2)用先乘后加类型的式子(1)等价表示呢?
a×mul + add(1)
(a + add)×mul = a×mul + add×mul(2)

由上,(2)与(1)唯一不同的是在更新a时,将add标记上乘mul,二者即等价,因此将(2)转化为(1)这样先乘后加的类型只需在添加乘法标记mul时先将加法标记add乘上mul即可将先加后乘转化为先乘后加的形式。
②再看先加后乘:
(a + add)×mul(1)
a×mul + add = a×mul + add×mul/mul = (a + add/mul)×mul(2)

​由上可见,如将(2)转化为(1)的形式则会出现add/mul,若此时mul不能整除add,则会造成精度损失,且如果存在取mod问题,则add/mul会存在求mul的逆元,极大影响了效率。
由上,改查操作时的lazytag下传顺序决定为先乘后加,那按这种顺序下传后对于下传到的区间的原有lazytag的修改是怎么实现的?
设:当前节点编号 o , addv[o]是当前节点的加法标记,mulv[o]是当前节点的乘法标记 sumv[o]是当前节点的求和值, ls表示左儿子的编号,rs表示右儿子的编号。(注:这里对当前结点的lazytag的定义为当前节点的 sumv值已维护好了,但是儿子的sumv和lazytag都还没维护好。)
以左儿子举例,根据先乘后加的顺序,我们先维护ls的sumv,即给儿子乘上自己的乘法标记,再加上自己的加法标记即可。
sumv[ls] = sumv[ls]×mulv[o] + addv[o]×(R-L+1)
此时ls的 sumv 值就维护好了,但如果ls还有lazytag呢?它的lazytag又该怎么维护?
已知:lazytag的下传顺序是先乘后加, 如果儿子已经有lazytag,它的lazytag如果要维护一个值 s, 它维护后的值 s’应该是:s′ = s×mulv[ls] + addv[ls]
那现在又给他加上了 o 的lazytag, 那么维护后的值 s’‘应该是:
s′′=s′×mulv[o] + addv[o]
= (s×mulv[ls] + addv[ls])×mulv[o] + addv[o]
= (s×mulv[ls]×mulv[o]) + (addv[ls]×mulv[o] + addv[o])(3)

设:mulv[ls]’ 、addv[ls] '为维护后的lazytag,能推出:
s’’=s×mulv[ls]′ + addv[ls]′(4)
又由(3)与(4)上式等价,我们即可得出儿子lazytag的维护方式:即在维护乘法标记时,直接在儿子s的乘法标记mul[s],维护加法标记时,先在儿子s的加法标记add[s]上乘一下自己的乘法标记mul[o],然后再加上自己的加法标记add[o]即可完成。
mulv[ls]′=mulv[ls]×mulv[o]
addv[ls]′=addv[ls]×mulv[o]+addv[o]

​至此,不论之前键入的修改顺序【此处是离线的】是对lazytag进行先乘后加还是先加后乘,我们人为规定一律都按先乘后加规则下的下传lazytag和求sumv的顺序求解,因此对于每个结点儿子s的sumv和lazytag,在每一步都被这种认为规定的固定规则等价维护,任务完成。
【以上分析部分源自luogu题解】

代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10,mod=10007;
struct node
{
    int l,r;
    int sum1,sum2,sum3;
    int add,mul,lazy;
}tr[N*4];
void pushup(node& p, node& l, node& r) 
{
    p.sum1 = (l.sum1 + r.sum1) % mod;
    p.sum2 = (l.sum2 + r.sum2) % mod;
    p.sum3 = (l.sum3 + r.sum3) % mod;
}
void pushup(int u)
{
    pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r)
{
    tr[u]={l,r,0,0,0,0,1,0};
    if(l==r) return;
    int mid=l+r>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    pushup(u);
}
void pushdown(node& op, int add, int mul, int lazy) 
{ //需要分类pushdown
    const int len = op.r - op.l + 1;
    if (lazy) {
        lazy %= mod;
        op.sum1 = lazy * len % mod;
        op.sum2 = lazy * lazy % mod * len % mod;
        op.sum3 = lazy * lazy % mod * lazy % mod * len % mod;
        
        op.add = 0, op.mul = 1; op.lazy = lazy;
    }
    
    if (mul != 1) { //对于乘法和加法的标签处理, 需要遵循先乘后加的原则
        op.sum1 = op.sum1 * mul % mod;
        op.sum2 = op.sum2 * mul % mod * mul % mod;
        op.sum3 = op.sum3 * mul % mod * mul % mod * mul % mod;
        
        op.add = mul * op.add % mod;
        op.mul = op.mul * mul % mod;
    }

    if (add) {
        op.sum3 = (op.sum3 + len * add % mod * add % mod * add % mod + 3 * op.sum2 % mod * add % mod + 3 * op.sum1 % mod * add % mod * add % mod) % mod;
        op.sum2 = (op.sum2 + 2 * add % mod * op.sum1 % mod + len * add % mod * add % mod) % mod;
        op.sum1 = (op.sum1 + len * add % mod) % mod;
        
        op.add = (op.add + add) % mod;
    }
}
void pushdown(int u)
{
    node &p=tr[u];
    pushdown(tr[u<<1],p.add,p.mul,p.lazy);
    pushdown(tr[u<<1|1],p.add,p.mul,p.lazy);
    p.add=0,p.mul=1,p.lazy=0;
}
void modify(int u,int pos,int l,int r,int c)
{
    if(l<=tr[u].l&&r>=tr[u].r)
    {
        if(pos==1) pushdown(tr[u],c,1,0);
        else if(pos==2) pushdown(tr[u],0,c,0);
        else pushdown(tr[u],0,1,c);
        return;
    }
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    if(l<=mid) modify(u<<1,pos,l,r,c);
    if(r>mid) modify(u<<1|1,pos,l,r,c);
    pushup(u);
}
int query(int u,int l,int r,int pos)
{
    if(l<=tr[u].l&&r>=tr[u].r)
    {
        if(pos==1) return tr[u].sum1;
        else if(pos==2) return tr[u].sum2;
        else return tr[u].sum3;
    }
    pushdown(u);
    int mid=tr[u].l+tr[u].r>>1;
    int res=0;
    if(l<=mid) res+=query(u<<1,l,r,pos);
    if(r>mid) res+=query(u<<1|1,l,r,pos);
    return res%mod;
}
int main()
{
    int n,m;
    while(scanf("%d %d", &n, &m), n | m)
    {
        build(1,1,n);
        for(int i=1;i<=m;i++)
        {
            int op,l,r,c;
            scanf("%d%d%d%d",&op,&l,&r,&c);
            if(op==4) printf("%d\n",query(1,l,r,c));
            else modify(1,op,l,r,c);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值