【模板】线段树 2
题目描述
如题,已知一个数列,你需要进行下面三种操作:
- 将某区间每一个数乘上 x x x;
- 将某区间每一个数加上 x x x;
- 求出某区间每一个数的和。
输入格式
第一行包含三个整数 n , q , m n,q,m n,q,m,分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 q q q 行每行包含若干个整数,表示一个操作,具体如下:
操作
1
1
1: 格式:1 x y k
含义:将区间
[
x
,
y
]
[x,y]
[x,y] 内每个数乘上
k
k
k
操作
2
2
2: 格式:2 x y k
含义:将区间
[
x
,
y
]
[x,y]
[x,y] 内每个数加上
k
k
k
操作
3
3
3: 格式:3 x y
含义:输出区间
[
x
,
y
]
[x,y]
[x,y] 内每个数的和对
m
m
m 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 3 3 3 的结果。
样例 #1
样例输入 #1
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
样例输出 #1
17
2
提示
【数据范围】
对于
30
%
30\%
30% 的数据:
n
≤
8
n \le 8
n≤8,
q
≤
10
q \le 10
q≤10。
对于
70
%
70\%
70% 的数据:$n \le 10^3
,
,
,q \le 10^4$。
对于
100
%
100\%
100% 的数据:
1
≤
n
≤
1
0
5
1 \le n \le 10^5
1≤n≤105,
1
≤
q
≤
1
0
5
1 \le q \le 10^5
1≤q≤105。
除样例外, m = 571373 m = 571373 m=571373。
(数据已经过加强 _)
样例说明:
故输出应为 17 17 17、 2 2 2( 40 m o d 38 = 2 40 \bmod 38 = 2 40mod38=2)。
大致思路
线段树模板,不过多解释,
首先,线段树是二叉树,因此具有二叉树的性质,其左儿子节点与右儿子节点是固定的,具体实现如下,其中, l c ( x ) lc(x) lc(x)为左儿子, r c ( x ) rc(x) rc(x)为右儿子(对应2n与2+1)
#define lc(x) (x<<1)
#define rc(x) ((x<<1)|1)
其次,线段树的建立为递归建立,最底层的节点对应的就是 a [ 1... n ] a[1...n] a[1...n]
void build(int x,int l,int r){
tag_add[x]=0;tag_mul[x]=1;
if(l==r){
sm[x]=a[l];
return;
}
int mid=(l+r)>>1;
build(lc(x),l,mid);
build(rc(x),mid+1,r);
pushup(x);
return;
}
s m [ x ] = s m [ l c ( x ) ] + s m [ r c ( x ) ] sm[x]=sm[lc(x)]+sm[rc(x)] sm[x]=sm[lc(x)]+sm[rc(x)]通常会被单独写做一个函数pushup
void pushup(int x){
sm[x]=(sm[lc(x)]+sm[rc(x)])%mod;
}
区间修改与查询
单点修改与查询只需如同建树一样查找到节点修改并pushup或return即可,不过多赘述。
对于区间修改,我们需要用到 lazy_tag 对于一次修改操作我们先不全部进行修改,当火烧眉毛不得不用到这个值时再进行修改,对于一种运算使用一个tag[]数组实现。
此模板题有两种运算,因此用 tag_add 与 tag_mul 分别记录
int sm[N<<2],a[N<<2],tag_add[N<<2],tag_mul[N<<2];
-
cover
- 两种运算,我们先乘后加
- 对于乘法,节点 x 对应的 sm[x] 就是一段区间的和,根据乘法分配律,我们直接 s m [ x ] ∗ m u l sm[x]*mul sm[x]∗mul 即可,同样, tag_add也要乘mul,已有的 tag_mul 根据乘法结合律,直接 t a g . m u l [ x ] ∗ m u l tag.mul [ x ] * mul tag.mul[x]∗mul,记得最后取模
void cover(int x,int l,int r,int ad,int mul){
sm[x]=sm[x]*mul%mod;
sm[x]+=(r-l+1)*ad%mod;
sm[x]%=mod;
tag_mul[x]*=mul;
tag_mul[x]%=mod;
tag_add[x]*=mul;
tag_add[x]+=ad;
tag_add[x]%=mod;
}
void pushdown(int x,int l,int r){
int mid=(l+r)>>1;
cover(lc(x),l,mid,tag_add[x],tag_mul[x]);
cover(rc(x),mid+1,r,tag_add[x],tag_mul[x]);
tag_add[x]=0;tag_mul[x]=1;
}
void update(int x,int l,int r,int L,int R,int ad,int mul){
// if(r<L||l>R)return;
if(l>=L&&R>=r){
cover(x,l,r,ad,mul);//若已被完全包含,进行一次计算
return;
}
pushdown(x,l,r);//注意下传tag
int mid=(l+r)>>1;
if(L<=mid)update(lc(x),l,mid,L,R,ad,mul);//下传左儿子
if(R>mid) update(rc(x),mid+1,r,L,R,ad,mul);//下传右儿子
// update(lc(x),l,mid,L,R,ad,mul);
// update(rc(x),mid+1,r,L,R,ad,mul);
pushup(x);
}
-
query
-
关键部分
- 实现区间询问,原理基本与 update 一致,注意区间被完全包含后返回值与取模。
- 同样给出三种写法,将注释掉的内容解开并将 if (L<=mid)…两行注释即为第二种写法
int query(int x,int l,int r,int L,int R){
// if(r<L||l>R)return 0;
int res=0;
if(l>=L&&R>=r){
return sm[x];
}
pushdown(x,l,r);
int mid=(l+r)>>1;
if(L<=mid)res+=(query(lc(x),l,mid,L,R)%mod);
if(R>mid) res+=(query(rc(x),mid+1,r,L,R)%mod);
// res+=(query(lc(x),l,mid,L,R)%mod);
// res+=(query(rc(x),mid+1,r,L,R)%mod);
return res%mod;
}
int query(int x,int l,int r,int L,int R){
if(r<L||l>R)return 0;
if(l>=L&&R>=r){
return sm[x];
}
pushdown(x,l,r);
int mid=(l+r)>>1;
return (query(lc(x),l,mid,L,R)+query(rc(x),mid+1,r,L,R))%mod;
}
真的快被线段树ex吐了
AC CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long int
const int N=1e6+2233;
#define lc(x) (x<<1)
#define rc(x) ((x<<1)|1)
int n,m,mod;
int sm[N<<2],a[N<<2],tag_add[N<<2],tag_mul[N<<2];
void pushup(int x){
sm[x]=(sm[lc(x)]+sm[rc(x)])%mod;
}
void build(int x,int l,int r){
tag_add[x]=0;tag_mul[x]=1;
if(l==r){
sm[x]=a[l];
return;
}
int mid=(l+r)>>1;
build(lc(x),l,mid);
build(rc(x),mid+1,r);
pushup(x);
return;
}
void cover(int x,int l,int r,int ad,int mul){
sm[x]=sm[x]*mul%mod;
sm[x]+=(r-l+1)*ad%mod;
sm[x]%=mod;
tag_mul[x]*=mul;
tag_mul[x]%=mod;
tag_add[x]*=mul;
tag_add[x]+=ad;
tag_add[x]%=mod;
}
void pushdown(int x,int l,int r){
int mid=(l+r)>>1;
cover(lc(x),l,mid,tag_add[x],tag_mul[x]);
cover(rc(x),mid+1,r,tag_add[x],tag_mul[x]);
tag_add[x]=0;tag_mul[x]=1;
}
void update(int x,int l,int r,int L,int R,int ad,int mul){
// if(r<L||l>R)return;
if(l>=L&&R>=r){
cover(x,l,r,ad,mul);
return;
}
pushdown(x,l,r);
int mid=(l+r)>>1;
if(L<=mid)update(lc(x),l,mid,L,R,ad,mul);
if(R>mid) update(rc(x),mid+1,r,L,R,ad,mul);
// update(lc(x),l,mid,L,R,ad,mul);
// update(rc(x),mid+1,r,L,R,ad,mul);
pushup(x);
}
int query(int x,int l,int r,int L,int R){
// if(r<L||l>R)return 0;
int res=0;
if(l>=L&&R>=r){
return sm[x];
}
pushdown(x,l,r);
int mid=(l+r)>>1;
if(L<=mid)res+=(query(lc(x),l,mid,L,R)%mod);
if(R>mid) res+=(query(rc(x),mid+1,r,L,R)%mod);
// res+=(query(lc(x),l,mid,L,R)%mod);
// res+=(query(rc(x),mid+1,r,L,R)%mod);
return res%mod;
}
signed main(){
scanf("%lld %lld %lld",&n,&m,&mod);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
build(1,1,n);
while(m--){
int op,xx,yy,kk;
scanf("%lld",&op);
if(op==1){
scanf("%lld %lld %lld",&xx,&yy,&kk);
update(1,1,n,xx,yy,0,kk);
}
if(op==2){
scanf("%lld %lld %lld",&xx,&yy,&kk);
update(1,1,n,xx,yy,kk,1);
}
if(op==3){
scanf("%lld %lld",&xx,&yy);
printf("%lld\n",query(1,1,n,xx,yy));
}
}
return 0;
}