蒟蒻的学习之——线段树2

线段树进阶

模板二相对于模板一,加了个区间乘,于是在模板一的基础上需要多开个数组(记录乘法懒标记)、多写个函数(区间乘),还有要把懒标记下放函数做些修改。

结构体内的变量定义:

sum[]:线段树节点对应区间的元素总和;

addv[]:线段树节点对应区间的所有元素待加的值(懒标记)。

mulv[]:线段树节点对应区间的所有元素待乘的值(懒标记)。

struct node
{
    ll sum;
    ll addv;
    ll mulv;
}t[maxn<<2];
过程说明:
  1. 建树(Build):不再累述,但是要把addv[]初值全部设为0,mulv[]初值全部设为1。
void build(ll k,ll l,ll r)
{
    t[k].addv=0;
    t[k].mulv=1;
    if(l==r)
    {
        scanf("%lld",&t[k].sum);
        return;
    }
    ll mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid + 1,r);
    update(k);
}
  1. update更新,加个取模就OK
void update(ll k)
{
    t[k].sum = (t[k<<1].sum + t[k<<1|1].sum) % p;
}
  1. 懒标记下放(Pushdown):

原理解释

1.当对某区间执行加法操作时,由于加法优先级低,不会对乘法操作产生影响,故直接相加即可;

2.当对某区间执行乘法操作时,由于乘法优先级高,会对之前的加法操作产生影响,故需要在相乘时不仅对sum和mulv相乘,也需要对addv相乘;

3.由于上述原因,故需要先算乘法再算加法。

void pushdown(ll k,ll l,ll r)
{
    ll mid=(l+r)>>1;
    t[k<<1].sum =(t[k<<1].sum * t[k].mulv + t[k].addv * (mid - l + 1)) % p;
    t[k<<1|1].sum = (t[k<<1|1].sum * t[k].mulv + t[k].addv * (r- (mid + 1) + 1)) % p;
    //儿子的值=儿子的值*爸爸的乘法lazytag+爸爸的加法lazytag*儿子的区间长度
    t[k<<1].mulv = (t[k<<1].mulv * t[k].mulv) % p;
    t[k<<1|1].mulv = (t[k<<1|1].mulv * t[k].mulv) % p;
    t[k<<1].addv = (t[k<<1].addv * t[k].mulv + t[k].addv) % p;
    t[k<<1|1].addv = (t[k<<1|1].addv * t[k].mulv +t[k].addv) % p;//维护lazytag 
    t[k].addv = 0;
    t[k].mulv = 1;//父节点初始化 
}

细节实现:

1.子树的sum、mulv、addv值分别乘上当前节点的mulv值;

2.子树的addv值加上当前节点的addv值;

3.子树的sum值加上(子树包含元素数量*当前节点的addv值);

4.当前节点的addv值还原,即置为0 ;

5.当前节点的mulv值还原,即置为1;

特别说明:

1.当前节点的懒标是否需要判断?其实判不判都没影响,判断了那么就会快一点,如果为空则不需执行此下放函数。虽然执行了也不会有太大影响(好像前面乘一个常数2?)

2.为尽量节省时间,要将判断放在此函数外而不是函数内,就是pushdown函数的外面判断

  1. 区间乘(Mul):
void mul(ll k,ll l,ll r,ll x,ll y,ll v)
{
    if(l>=x && r<=y)
    {
        t[k].sum = (t[k].sum * v) % p;
        t[k].mulv = (t[k].mulv * v) % p;
        t[k].addv = (t[k].addv * v) % p;
        return;
    }
    if(x>r || y<l ) return;
    if(t[k].mulv!=1 || t[k].addv ) pushdown(k,l,r);
    ll mid=(l+r)>>1;
    mul(k<<1,l,mid,x,y,v);
    mul(k<<1|1,mid+1,r,x,y,v);
    update(k);
}

若当前节点完全包含在待更新区间内,则直接修改当前节点的mulv、addv、sum值

  1. 区间加(Add):跟乘一样
void add(ll k,ll l,ll r,ll x,ll y,ll v)
{
    if(l>=x && r<=y)
    {
        t[k].addv = (t[k].addv + v) % p;
        t[k].sum = (t[k].sum+(r-l+1)*v) % p;
        return;
    }
    if(x>r || y<l) return;
    if(t[k].mulv!=1 || t[k].addv ) pushdown(k,l,r);
    ll mid=(l+r)>>1;
    add(k<<1,l,mid,x,y,v);
    add(k<<1|1,mid+1,r,x,y,v);
    update(k);
}
  1. 区间查询(Query):同模板一。。。
ll query(ll k,ll l,ll r,ll x,ll y)
{
    ll ans=0;
    if(l>=x && r<=y) return t[k].sum;
    if(t[k].mulv!=1 || t[k].addv ) pushdown(k,l,r);
    ll mid = (l + r) >> 1;
    if(x<=mid) ans+=query(k<<1,l,mid,x,y);
    if(mid<y) ans+=query(k<<1|1,mid + 1,r,x,y);
    return ans % p;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值