线段树(加法和乘法的模板)

45 篇文章 1 订阅
20 篇文章 0 订阅

所有的操作都没什么很大的变化
我们处理加法和乘法的时候,默认乘法操作优先于加法操作

在计算一个结点的值的时候:
val=val*mul+len*add
mul:乘法标记
len:区间长度
add:加法标记

在下传标记的时候,
儿子的值我们也是按照上面那样算

注意,标记代表的是对当前结点已经进行过的操作,但是还没有对儿子进行过该操作

整个算法中,最难的就是儿子的标记维护

child.mul=child.mul*fa.mul

child.add=child.add*fa.mul+fa.add

  • 儿子节点的add标记分别先乘上父亲节点的乘标记再加上父亲节点的加标记
    (因为父亲节点的加标记已经乘上了一些乘标记了,不需要再乘一次)
  • 儿子节点的乘标记直接乘上父亲节点的乘标记。

tip

开ll
随时%
mul标记的初始值是1,add标记初始值是0
在修改值的时候,add的维护需要累加,mul的维护需要累乘

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

const int N=100010;
struct node{
    int x,y;
    ll v,mul,ad;
};
node t[N<<2];
int n,m;
ll a[N],p;

void update(int bh)
{
    t[bh].v=t[bh<<1].v+t[(bh<<1)+1].v;
    t[bh].v%=p;
}

void push(int bh)        //默认乘操作在加操作之前 
{
    int lc=bh<<1;
    int rc=(bh<<1)+1;
    if (t[bh].mul!=1||t[bh].ad!=0)
    {
        t[lc].v=( (t[lc].v*t[bh].mul)%p + ((t[lc].y-t[lc].x+1)*t[bh].ad)%p )%p;
        t[lc].mul=(t[bh].mul*t[lc].mul)%p;
        t[lc].ad=(t[lc].ad*t[bh].mul)%p+t[bh].ad; t[lc].ad%=p;

        t[rc].v=( (t[rc].v*t[bh].mul)%p + ((t[rc].y-t[rc].x+1)*t[bh].ad)%p )%p;
        t[rc].mul=(t[bh].mul*t[rc].mul)%p;
        t[rc].ad=(t[rc].ad*t[bh].mul)%p+t[bh].ad; t[rc].ad%=p;

        t[bh].ad=0;
        t[bh].mul=1;
    }
    return;
}

void build(int bh,int l,int r)
{
    t[bh].x=l;
    t[bh].y=r;
    t[bh].mul=1; t[bh].ad=0;
    if (l==r)
    {
        t[bh].v=a[l]%p;
        return;
    }
    int mid=(l+r)>>1;
    build(bh<<1,l,mid);
    build(bh<<1|1,mid+1,r);
    update(bh);
}

void add(int bh,int l,int r,ll val)
{
    push(bh);
    if (t[bh].x>=l&&t[bh].y<=r)
    {
        t[bh].ad+=val; t[bh].ad%=p;
        t[bh].v=( t[bh].v*t[bh].mul%p + ((t[bh].y-t[bh].x+1)*t[bh].ad)%p )%p;
        return;
    }
    int mid=(t[bh].x+t[bh].y)>>1;
    if (l<=mid) add(bh<<1,l,r,val);
    if (r>mid) add((bh<<1)+1,l,r,val);
    update(bh);
}

void multi(int bh,int l,int r,ll val)
{
    push(bh);
    if (t[bh].x>=l&&t[bh].y<=r)
    {
        t[bh].mul*=val; t[bh].mul%=p;
        t[bh].v=( t[bh].v*t[bh].mul%p + ((t[bh].y-t[bh].x+1)*t[bh].ad)%p )%p;
        return;
    }
    int mid=(t[bh].x+t[bh].y)>>1;
    if (l<=mid) multi(bh<<1,l,r,val);
    if (r>mid) multi((bh<<1)+1,l,r,val);
    update(bh);
}

int ask(int bh,int l,int r)
{
    push(bh);
    if (t[bh].x>=l&&t[bh].y<=r)
        return t[bh].v%p;
    ll ans=0;
    int mid=(t[bh].x+t[bh].y)>>1;
    if (l<=mid) ans+=ask(bh<<1,l,r);
    if (r>mid) ans+=ask((bh<<1)+1,l,r);
    return ans%p;
}

int main()
{
    scanf("%d%d%lld",&n,&m,&p);
    for (int i=1;i<=n;i++) scanf("%lld",&a[i]);

    build(1,1,n);

    int opt,x,y;
    ll z;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&opt,&x,&y);
        if (opt==3){
            printf("%lld\n",ask(1,x,y)%p);
        }
        else if (opt==2)
        {
            scanf("%lld",&z);
            add(1,x,y,z%p);
        }
        else 
        {
            scanf("%lld",&z);
            multi(1,x,y,z%p);
        }
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值