题解 P3373 【【模板】线段树 2】

· · 题解

做完后Cry了,呜呜呜,擦干眼泪写题解~

咳咳...

本代码非结构体~

敲黑板啦!

今天就让本 蒟蒻 带领您 完全解读代码吧(用 蒟蒻 的思维看世界~)

  1. 前言:看了一下各位大佬写的代码我是非常非常的佩服,但我看了一下大多数都使用了“结构体”(struct..)所以我就写了一份不用“结构体”的代码,简介易懂,适用于(在1s前刚刚学会正常的线段树(即区间求和和区间询问,仅仅是对于某区间加法的辣种))
  2. 准备阶段:若您一点都不了解“线段树”的话,强烈建议您先把此题目做出来:https://www.luogu.org/problemnew/show/P3372 。您可以参考这篇文档入门(请看最前面点赞数超过1000+的辣一位,作者:_皎月半洒花):https://www.luogu.org/problemnew/solution/P3372

再谈线段树:

在线段树中,一开始我觉得最难的就是(lazy_tag延迟标记,也就是您看到的add数组,mul数组),这是什么东西啊,内心是拒绝的,我想:每次更新的时候顺便把所有的子节点全部更新不就OK了吗?但是后来再想想,这样做是很浪费滴(浪费是可耻的哦~),因为您看(见下图),当我们想要知道sum[1..6]的时候,您只需要询问1号节点即可,无论是修改还是询问,其实很多子节点如果不需要,您是没有必要去修改它的,您可以用数组(add数组,mul数组,即延迟标记)把它们暂时存储起来,当您想要用道的时候,在一起更新子节点,甚至对于多次修改时候更加巧妙,若您想两次修改区间同一个区间[1..2]时,即打了两次延迟标记,即更加节省时间,更能体现它的作用,在往下推广,即多个区间中有交集,则其中有线段树节点是被重复修改的,所以使用延迟标记等下次用到的时候再更新,所以引入了延迟标记来避免多次更新,减少浪费。所以它是一个动态(这个词是在本蒟蒻绞尽脑汁想出来的,未知是否规范)的。

再谈此题:

我们可以看道的是,题目中多了一个让区间乘法的操作,What?什么玩意,头脑顿时失了智~

等冷静下来您可以发现如下事实:(见下图) 最后您需要更新对应的节点值:tr[p]=( tr[p]Xmulk+(r-l+1)Xsumk)%mo;(“X”是乘法的意思)

当然您会发现:

ll query(ll p,ll l,ll r,ll x,ll y){
    if (x<=l&&r<=y) return tr[p] ;
    pushdown(p,l,r);
    ll mid=(l+r)/2,res=0;
    if (x<=mid) res+=query(p*2,l,mid,x,y);
    if (mid<y) res+=query(p*2+1,mid+1,r,x,y);
    return res%mo;
}

在最后没有更新对应节点的值(“tr[p]=( tr[p]Xmulk+(r-l+1)Xsumk)%mo;(“X”是乘法的意思)”):这是因为您并没有修改子节点,故无需重新计算。

ADD函数即算出对应节点应该有的值,和对应的add,mul(延迟标记)注意,此函数不仅在pushdown中被使用了一次,在以change开头的函数中均有调用。 changesum是算加法;changemul算的是乘法。

总共84行,因为思路过于清晰,代码过于简洁无需注释

#include<bits/stdc++.h>
#define ll long long
#define N 10000005 
using namespace std;
ll a[N],tr[N*4],mo,mul[N*4],add[N*4],n,m;
void build(ll p,ll l,ll r){//我不是注释:建造
    mul[p]=1;
    if (l==r){
        tr[p]=a[l];
        return ;
    }
    ll mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    tr[p]=(tr[p*2]+tr[p*2+1])%mo;
    return ;
}
void ADD(ll p,ll l,ll r,ll mulk,ll sumk){
    tr[p]=(tr[p]*mulk+(r-l+1)*sumk)%mo;
    add[p]=(add[p]*mulk+sumk)%mo;
    mul[p]=(mul[p]*mulk)%mo;
    return ;
}
void pushdown(ll p,ll l,ll r){
    ll mid=(l+r)/2;
    ADD(p*2,l,mid,mul[p],add[p]);//我不是注释:下传递两个字节点
    ADD(p*2+1,mid+1,r,mul[p],add[p]);
    add[p]=0;
    mul[p]=1;
    return ;
}
void changemul(ll p,ll l,ll r,ll x,ll y,ll v){
    if (x<=l&&r<=y){
        ADD(p,l,r,v,0);
        return ;
    }
    pushdown(p,l,r);
    ll mid=(l+r)/2;
    if (x<=mid) changemul(p*2,l,mid,x,y,v);//我不是注释:看是否还有包含的交集,下同理
    if (mid<y) changemul(p*2+1,mid+1,r,x,y,v);
    tr[p]=(tr[p*2]+tr[p*2+1])%mo;
}
void changesum(ll p,ll l,ll r,ll x,ll y,ll v){
    if (x<=l&&r<=y){
        ADD(p,l,r,1,v);
        return ;
    }
    pushdown(p,l,r);
    ll mid=(l+r)/2;
    if (x<=mid) changesum(p*2,l,mid,x,y,v);
    if (mid<y) changesum(p*2+1,mid+1,r,x,y,v);
    tr[p]=(tr[p*2]+tr[p*2+1])%mo;
}
ll query(ll p,ll l,ll r,ll x,ll y){//我不是注释:区间询问
    if (x<=l&&r<=y) return tr[p] ;
    pushdown(p,l,r);
    ll mid=(l+r)/2,res=0;
    if (x<=mid) res+=query(p*2,l,mid,x,y);
    if (mid<y) res+=query(p*2+1,mid+1,r,x,y);
    return res%mo;
}
int main(){
    scanf("%lld %lld %lld",&n,&m,&mo);//我不是注释:注意%lld
    for (ll i=1;i<=n;i++)
    scanf("%lld",&a[i]);
    build(1,1,n);
    for (ll i=1;i<=m;i++){
        ll n1=0;ll x,y,k;
        scanf("%lld",&n1);
        if (n1==1){
            scanf("%lld %lld %lld",&x,&y,&k);
            changemul(1,1,n,x,y,k);
        }
        if (n1==2){
            scanf("%lld %lld %lld",&x,&y,&k);
            changesum(1,1,n,x,y,k);
        }
        if (n1==3){
            scanf("%lld %lld",&x,&y);
            printf("%lld\n",query(1,1,n,x,y));
        }
    }
    return 0;
} 

蟹蟹,momoda~