线段树_区间加乘(洛谷P3373模板)

题目描述

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

1.将某区间每一个数乘上x

2.将某区间每一个数加上x

3.求出某区间每一个数的和

输入格式:

第一行包含三个整数N、M、P,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含3或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k

操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k

操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果

输出格式:

输出包含若干行整数,即为所有操作3的结果。

线段树维护区间加、区间乘。

相比最普通的线段树,我们这里在每一个节点都存两个标记:

mark[ x ][ 0 ](加标记)表示对于节点x所表示的一整段区间,每个数通过加法操作所增加的值(就是说先加a再乘b再加c之后,这里存的数是a*b+c)

mark[ x ][ 1 ](乘标记)表示对于节点x所表示的一整段区间,每个数被乘了几次。

p[ x ]表示的一整段区间中每一个数的和是多少。

接下来考虑各种操作,假设我们要对[L,R]的每一个数进行操作,当前正在处理编号为x的[l,r]的区间(这里保证了不存在r<L或R<l,即[l,r]与[L,R]无交集,这种情况不做任何处理直接return就好)。

如果进行加操作,当L<=l,r<=R,即当前区间被完整覆盖,我们将mark[ x ][ 0 ]直接加上加的数,再更新一下p[ x ]的值。若当前区间不被完整覆盖,就pushdown一下,再处理递归子节点就好。

如果进行乘操作,当L<=l,r<=R,即当前区间被完整覆盖,我们将mark[ x ][ 1 ]与mark[ x ][ 0 ]都直接乘上乘的数,更新一下p[ x ]的值(因为从这之前这个区间每一个数通过加法操作所增加的数都被乘了)。若当前区间不被完整覆盖,同样的,就pushdown一下,再处理递归子节点就好。

接下来就是较为复杂的pushdown了:

首先pushdown的一大意义在于,一个区间上的标记表示的是区间内每一个数都具备的特点,当我们需要pushdown时,就说明我们对区间内部子区间进行操作,导致区间的标记不再具有普遍性,我们需要把标记向下传递,并把当前区间的标记清空。

所以pushdown分为以下几个部分:

1、将左右儿子区间的乘标记都乘上本区间的乘标记。

2、将左右儿子区间的加标记都先乘上本区间的乘标记,再加上本区间的加标记。

3、将左右儿子的区间和的值先乘上本区间乘标记,再分加上本区间加标记与左右儿子区间长度的乘积。

4、将本区间的标记清空,再在对左右儿子操作完成后用左右儿子的区间和更新本区间的区间和。

然后就是代码了。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 500000
#define mid (l+r>>1)
using namespace std;
LL read(){
    LL nm=0ll,fh=1ll;char cw=getchar();
    for(;!isdigit(cw);cw=getchar()) if(cw==‘-‘) fh=-fh;
    for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-‘0‘);
    return fh*nm;
}
LL n,mod,q,p[M],mark[M][2],a[M],L,R,qs,num;
void build(LL x,LL l,LL r){
    mark[x][1]=1;
    if(l==r){p[x]=a[l];return;}
    build(x<<1,l,mid),build(x<<1|1,mid+1,r);
    p[x]=(p[x<<1]+p[x<<1|1])%mod;
}
void up(LL x){p[x]=(p[x<<1]+p[x<<1|1])%mod;}
void pushdown(LL x,LL l,LL r){
    p[x<<1]=p[x<<1]*mark[x][1]%mod;
    p[x<<1]=(p[x<<1]+(mark[x][0]*(mid-l+1)))%mod;
    p[x<<1|1]=p[x<<1|1]*mark[x][1]%mod;
    p[x<<1|1]=(p[x<<1|1]+(mark[x][0]*(r-mid)))%mod;
    mark[x<<1][1]=mark[x<<1][1]*mark[x][1]%mod;
    mark[x<<1][0]=(mark[x<<1][0]*mark[x][1]+mark[x][0])%mod;
    mark[x<<1|1][1]=mark[x<<1|1][1]*mark[x][1]%mod;
    mark[x<<1|1][0]=(mark[x<<1|1][0]*mark[x][1]+mark[x][0])%mod;
    mark[x][0]=0ll,mark[x][1]=1ll;
}
void add(LL x,LL l,LL r,LL m){
    if(r<L||R<l) return;
    if(L<=l&&r<=R){
        mark[x][0]=(mark[x][0]+m)%mod;
        p[x]=(p[x]+(m*(r-l+1)))%mod;
        return;
    }
    pushdown(x,l,r);
    add(x<<1,l,mid,m),add(x<<1|1,mid+1,r,m);
    up(x);
}
void mult(LL x,LL l,LL r,LL m){
    if(r<L||R<l) return;
    if(L<=l&&r<=R){
        mark[x][0]=mark[x][0]*m%mod;
        mark[x][1]=mark[x][1]*m%mod;
        p[x]=p[x]*m%mod;
        return;
    }
    pushdown(x,l,r);
    mult(x<<1,l,mid,m),mult(x<<1|1,mid+1,r,m);
    up(x);
}
LL query(LL x,LL l,LL r){
    if(r<L||R<l) return 0ll;
    LL ans;
    if(L<=l&&r<=R) ans=p[x]%mod;
    else{
        pushdown(x,l,r);
        ans=(query(x<<1,l,mid)+query(x<<1|1,mid+1,r))%mod;
        up(x);
    }
    return ans;
}
int main(){
    n=read(),q=read(),mod=read();
    for(LL i=1;i<=n;i++) a[i]=read();
    build(1,1,n);
    while(q--){
        qs=read(),L=read(),R=read();
        if(qs==3) printf("%lld\n",query(1,1,n)%mod);
        else{
            num=read();
            if(qs==2) add(1,1,n,num);
            else mult(1,1,n,num);
        }
    }
    return 0;
}

  

原文地址:https://www.cnblogs.com/OYJason/p/8351599.html

时间: 2024-10-22 12:49:29

线段树_区间加乘(洛谷P3373模板)的相关文章

HDU_3308_线段树_区间合并

LCIS Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 6166    Accepted Submission(s): 2675 Problem Description Given n integers.You have two operations:U A B: replace the Ath number by B. (index

线段树的进阶使用(洛谷3373 )

题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含三个整数N.M.P,分别表示该数列数字的个数.操作的总个数和模数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k 操作2: 格式:2 x y k 含义:将区间[x,y]内

POJ 2777-Count Color(线段树_区间染色)

Count Color Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Submit Status Practice POJ 2777 Appoint description:  System Crawler  (2015-04-10) Description Chosen Problem Solving and Program design as an optional course, y

【模板】线段树(区间加)

代码复杂度较高的数据结构--写过的最长的模板(你才写过几个模板啊) 有点类似分块的思想--??? 设置add的标记,省去一个个节点修改的很多时间 在修改&查询中不断维护父子关系 #include <algorithm> #include <iostream> #include <cstdio> #include <cmath> #define ll long long #define MAXN 1000001 #define leftson cur&

线段树维护区间开方/除法

今天考试考了一些神仙数据结构 T1 线段树维护区间加,区间开方,区间和 (数据范围:5e5) T2 线段树维护区间加,区间除,区间和,区间最值 对于这些题目,就像是之前考的区间与,区间或一样,除法,开方的操作会让各个数字之间越来越相近,最后变成一串一串连续的数字都是一样的,所以对于这一部分的操作我们一定程度上使用暴力,而如果一段都相等就相当于直接进行区间剪发的操作 那么我们来看如何判断区间一段都相等,那我们只需要维护区间的最值,最小值==最大值就完全相等了 然后.....代码.....被 \(y

洛谷 P3373 【模板】线段树 2

P3373 [模板]线段树 2 题目描述 如题,已知一个数列,你需要进行下面三种操作: 1.将某区间每一个数乘上x 2.将某区间每一个数加上x 3.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含三个整数N.M.P,分别表示该数列数字的个数.操作的总个数和模数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k 操作2: 格式:2 x

Super Mario(线段树离线区间k值)

以前见过这题,没做出来,知道是离线处理,这次仔细想了下, 首先把出现的高度都map离散化一下,以离散化出来的数目g建树,把每个位置都开俩个vector,一个存以这个位置为L的询问,一个存以这个位置为R的询问. 然后从1-g 进行更新,假如当前i是以第j个区间的开始位置,那么这时就可以询问一下<=p[j].h的个数s,显然这时第J个区间多加的,需要减掉,p[j].sum-=s; 然后更新第i个数,update(a[i],1,g,1);再找到某第k区间是以i结尾的,那么依旧询问一下,得出s,p[k]

UVA 12436-Rip Van Winkle&#39;s Code(线段树的区间更新)

题意: long long data[250001]; void A( int st, int nd ) { for( int i = st; i \le nd; i++ ) data[i] = data[i] + (i - st + 1); } void B( int st, int nd ) { for( int i = st; i \le nd; i++ ) data[i] = data[i] + (nd - i + 1); } void C( int st, int nd, int x

刷题向》关于线段树的区间开根号 BZOJ3211

这是一道关于线段树的区间开根号的裸题,没什么好讲的. 值得注意的是,因为有区间开根号的性质,所以我们每一次更改操作只能把更改区间所覆盖的所有元素全部查找,当然你直接找效率明显爆炸... 能够注意到,指数级别的操作一次更改的数字都很大,而题目的数字最大是10的9次,所以可以注意到的是当一个区间更新6遍以后就失去更新的意义了,因为当你更改次数超过6次所有非负整数数字就全部会化为1.所以可以在每一个节点上加一个类似于LAZY标记的东西,记录开根号次数,以便节约跟新时间. 贴出题目&代码 Descrip