学习记录 2021.10.2

如题,已知一个数列,你需要进行下面三种操作:

  1. 将某区间每一个数乘上x
  2. 将某区间每一个数加上x
  3. 求出某区间每一个数的和对p取模的结果

很明显这又是一个RMQ的题目,可以用线段树求解。
(以下均假设区间长度为len,原来的和为x)

前提:很明显,如果某一个区间同时加上一个数a,那么整个区间的和变为x+len*a;如果同时乘a,那么和就变为ax。

然而本题有两种修改操作,使用1种懒标记显然是不够的。所以用2种,分别为加法标记addt,乘法标记mult。在有两种标记时,由于标记要下放,所以
必须人为规定两种标记的更新顺序。
比如,对某个区间先进行+add1,再 *mul,再+add2,其和变为
m u l ( x + a d d 1 ) + a d d 2 mul(x+add1)+add2 mul(x+add1)+add2,我们记它为true_res
如果认为加法优先,那么和就变为

m u l ( x + a d d 1 + a d d 2 ) mul(x+add1+add2) mul(x+add1+add2)
想让它变为true_res,做法就是令add2/mul。
进一步得到:在某次乘法之后进行的加法,要先把加上的数除以乘过的值mult(不知道这样理解对不对)。然而这很可能会导致除出浮点数。由于计算机中浮点数存储存在误差,所以此做法不可行。

如果认为乘法优先,那么和就变为
x ∗ m u l + a d d 1 + a d d 2 x*mul+add1+add2 xmul+add1+add2
想让它变为true_res,做法是让先加的 add1乘上mul。
进一步得到以下结论:在某次乘法之前进行的加法,在进行当这次乘法后必须乘上要乘的值,而在之后进行的加法,则不需要这一操作。

这样,在下放懒标记时,可以让

  1. 左右儿子的值:先乘 mult[fa],再加 len * addt[fa]
  2. 左右儿子的addt:先乘 mul[fa],再加 addt[fa]
  3. 左右儿子的mult:由于乘法有优先性,故直接乘mul[fa]即可

代码如下:

struct seg_tree {
    long data;
    long addt,mult;
    inline seg_tree():data(0),addt(0),mult(1) {}
};
seg_tree a[M*4];
int n,p;
inline void set_tag(int son,int f,int child_len) {
    a[son].data=(a[son].data*a[f].mult+child_len*a[f].addt)%p;
    a[son].addt=(a[son].addt*a[f].mult+a[f].addt)%p;  //?
    a[son].mult=(a[son].mult*a[f].mult)%p;
}

inline void push_down(int k,int l,int r) {
    int lc=k*2+1,rc=k*2+2;
    int child_len=(r-l+1)/2;
    set_tag(lc,k,child_len);
    set_tag(rc,k,child_len);
    a[k].addt=0;
    a[k].mult=1;
}

void add(int st,int end,long val,int k=0,int l=0,int r=n) {
    if(l>end||r<st)    return;
    if(l>=st&&r<=end) {
        a[k].addt=(a[k].addt+val)%p;
        a[k].data=(a[k].data+(r-l+1)*val)%p;
        return;
    }
    push_down(k,l,r);
    int mid=(l+r)/2,lc=k*2+1,rc=k*2+2;
    add(st,end,val,lc,l,mid);
    add(st,end,val,rc,mid+1,r);
    a[k].data=(a[lc].data+a[rc].data)%p;
}

void mul(int st,int end,long val,int k=0,int l=0,int r=n) {
    if(l>end||r<st)    return;
    if(l>=st&&r<=end) {
        a[k].mult=(a[k].mult*val)%p;
        //注意:这一步必须有,因为在本次乘法之前进行的加法,在进行当本次乘法后必须乘上要乘的值
        a[k].addt=(a[k].addt*val)%p;
        //区间内所有数*k,则整个区间的和*k
        a[k].data=(a[k].data*val)%p;
        return;
    }
    push_down(k,l,r);
    int mid=(l+r)/2,lc=k*2+1,rc=k*2+2;
    mul(st,end,val,lc,l,mid);
    mul(st,end,val,rc,mid+1,r);
    a[k].data=(a[lc].data+a[rc].data)%p;
}

long query(int st,int end,int k=0,int l=0,int r=n) {
	if(l>end||r<st)    return 0;
	if(l>=st&&r<=end)	return a[k].data;
	push_down(k,l,r);
	int lc=k*2+1,rc=k*2+2;
	long L=query(st,end,lc,l,(l+r)/2);
	long R=query(st,end,rc,1+(l+r)/2,r);
	return (L+R)%p;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值