BZOJ1798:[Ahoi2009]Seq 维护序列seq(线段树既有乘又有加的LAZY操作)

传送门

题意:给出一个线段,对这个线段的部分既有一段相乘一个数,又有一段相加上一个数,还有中间会询问一段区间的和是多少?

题解:如果只有一段相加上一个数,那么很显然,很板的LAZY操作,但是现在又有了相乘一个数的操作,还在LAZY操作进行解决

现在区间不仅有加法,还有乘法,区间和的形式应该为:ax+b,表示现在的区间和是原来的区间和先乘以a再加上b。

在程序中我们把mu定义为a,sum定义成su,ad定义为b,下传标记时节点的更新就是su = mu * su + ad

当我们要修改一个区间时,要保证ax+b的形式,即先乘后加的形式。当将区间乘以一个数k时,原来的区间和为ax+b,乘以k得k(ax+b)=kax+kb,也就是把节点的su和ad都乘上一个k。

区间加一个数更加简单,原来的区间和为ax+b,加上一个k为ax+b+k,合并b, k得ax+(b+k),也就是把原来的ad加上一个k。

我们设要下传标记的节点的ad为b,mu为a,因此这个节点的和为ax+b,它的一个儿子的ad为b',mu为a',这个节点的和为a'y+b',为了保持先乘后加的顺序,先把该节点的和乘以a得aa'y+ab'然后加上b得aa'y+ab'+b合并一下得(aa')y+(ab'+b)也就是把这个节点的儿子的mu乘以这个节点的mu,然后把这个节点的儿子的ad乘以这个节点的mu再加上这个节点的ad,更新这个节点,清空这个节点的标记,然后标记就下传完毕了。

附上代码:

#include<bits/stdc++.h>
#define update tr[k].su=(tr[k<<1].su+tr[k<<1|1].su)%P
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
struct data{int l,r;ll su,mu,ad;}tr[maxn<<2];
int N,P,M,op,t,g,c;
void build(int k,int l,int r)
{
    tr[k].l=l;tr[k].r=r;tr[k].mu=1;tr[k].ad=0;
    if(l==r){
        ll x;scanf("%lld",&x);
        tr[k].su=x%P;return ;
    }
    int mid=(l+r)>>1;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    update;
}
void maintain(int k,int L){
    tr[k<<1].su=(tr[k<<1].su*tr[k].mu+tr[k].ad*(L+1>>1))%P;
    tr[k<<1|1].su=(tr[k<<1|1].su*tr[k].mu+tr[k].ad*(L>>1))%P;
    tr[k<<1].mu=tr[k<<1].mu*tr[k].mu%P;
    tr[k<<1|1].mu=tr[k<<1|1].mu*tr[k].mu%P;
    tr[k<<1].ad=(tr[k<<1].ad*tr[k].mu+tr[k].ad)%P;
    tr[k<<1|1].ad=(tr[k<<1|1].ad*tr[k].mu+tr[k].ad)%P;
    tr[k].mu=1;tr[k].ad=0;
}
void cheng(int k,int x,int y,int val)
{
    int l=tr[k].l,r=tr[k].r;
    if(l==x&&r==y){
        tr[k].mu=(tr[k].mu*val)%P;
        tr[k].ad=(tr[k].ad*val)%P;
        tr[k].su=(tr[k].su*val)%P;
        return ;
    }
    maintain(k,r-l+1);
    int mid=(l+r)>>1;
    if(mid>=y)cheng(k<<1,x,y,val);
    else if(mid<x)cheng(k<<1|1,x,y,val);
    else{
        cheng(k<<1,x,mid,val);cheng(k<<1|1,mid+1,y,val);
    }
    update;
}
void jia(int k,int x,int y,int val)
{
    int l=tr[k].l,r=tr[k].r;
    if(x==l&&y==r){
        tr[k].ad=(tr[k].ad+val)%P;
        tr[k].su=(tr[k].su+val*(r-l+1))%P;
        return ;
    }
    maintain(k,r-l+1);
    int mid=(l+r)>>1;
    if(mid>=y)jia(k<<1,x,y,val);
    else if(mid<x)jia(k<<1|1,x,y,val);
    else{
        jia(k<<1,x,mid,val);jia(k<<1|1,mid+1,y,val);
    }
    update;
}
int ask(int k,int x,int y)
{
    int l=tr[k].l,r=tr[k].r;
    if(l==x&&y==r)return tr[k].su%P;
    maintain(k,r-l+1);
    int mid=(l+r)>>1;
    if(mid>=y)return ask(k<<1,x,y);
    else if(mid<x)return ask(k<<1|1,x,y);
    else return (ask(k<<1,x,mid)+ask(k<<1|1,mid+1,y))%P;
}
int main()
{
    scanf("%d%d",&N,&P);
    build(1,1,N);
    scanf("%d",&M);
    while(M--){
        scanf("%d%d%d",&op,&t,&g);
        if(op==1){
            scanf("%d",&c);
            cheng(1,t,g,c);
        }else if(op==2){
            scanf("%d",&c);
            jia(1,t,g,c);
        }else printf("%d\n",ask(1,t,g));
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值