线段树
洛谷上有两道线段树模板(指模板1,模板2)都是区间维护的,也就是说,都离不开lasytag
的维护,为了提高效率,故使用了lasytag
,这里看一下题
【模板】线段树 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 k k k。
- 求出某区间每一个数的和。
输入格式
第一行包含两个整数 n , m n, m n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。
接下来 m m m 行每行包含 3 3 3 或 4 4 4 个整数,表示一个操作,具体如下:
1 x y k
:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k。2 x y
:输出区间 [ x , y ] [x, y] [x,y] 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
样例 #1
样例输入 #1
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
样例输出 #1
11
8
20
提示
对于
30
%
30\%
30% 的数据:
n
≤
8
n \le 8
n≤8,
m
≤
10
m \le 10
m≤10。
对于
70
%
70\%
70% 的数据:
n
≤
10
3
n \le {10}^3
n≤103,
m
≤
10
4
m \le {10}^4
m≤104。
对于
100
%
100\%
100% 的数据:
1
≤
n
,
m
≤
10
5
1 \le n, m \le {10}^5
1≤n,m≤105。
保证任意时刻数列中所有元素的绝对值之和 ≤ 10 18 \le {10}^{18} ≤1018。
【样例解释】
分析1.1
操作很少,只有:
- 区间加
- 区间求和
我们用数组存储线段树每个节点的状态即可,看代码:
代码1
#include <bits/stdc++.h>
using namespace std;
const int M = 1e5+10;
#define int long long
int a[M];
struct segment {
#define lc(p) (p<<1)
#define rc(p) ((p<<1)|1)
int tag[M << 2], sum[M << 2];
void f(int o, int l, int r, int k) {
tag[o] += k;
sum[o] += k * (r - l + 1);
}
void push_up(int x) {
sum[x] = sum[lc(x)] + sum[rc(x)];
}
void push_down(int o, int l, int r) {
int mid = l + r >> 1;
f(lc(o), l, mid, tag[o]);
f(rc(o), mid + 1, r, tag[o]);
tag[o] = 0;
}
void build(int o, int l, int r) {
if (l == r) { sum[o] = a[l]; return; }
int mid = l + r >> 1;
build(lc(o), l, mid);
build(rc(o), mid + 1, r);
push_up(o);
}
int query(int ql, int qr, int o, int l, int r) {
if (l>qr or r<ql) return 0;
if (ql <= l and r <= qr) return sum[o];
int ans = 0;
int mid = l + r >> 1;
push_down(o, l, r);
ans += query(ql, qr, lc(o), l, mid);
ans+=query(ql,qr,rc(o),mid+1,r);
return ans;
}
void update(int ql,int qr,int o,int l,int r,int k){
if (l>qr or r<ql) return;
if(ql<=l and r<=qr) {f(o,l,r,k);return;}
push_down(o,l,r);
int mid = l + r >> 1;
update(ql, qr, lc(o), l, mid,k);
update(ql,qr,rc(o),mid+1,r,k);
push_up(o);
}
}t1;
signed main() {
int n,m;
cin >> n>>m;
for (int i = 1; i <= n; i++) cin >> a[i];
t1.build(1,1,n);
for (int i=1;i<=m;i++){
int x,y,k;
cin>>x;
if (x==1){
cin>>x>>y>>k;
t1.update(x,y,1,1,n,k);
}
else{
cin>>x>>y;
cout<<t1.query(x,y,1,1,n)<<endl;
}
}
return 0;
}
分析1.2
我们只需要理解segment结构体
- sum[i]表示线段树第i个节点对应的区间和
- tag[i]就是节点i的懒标记
所以我们需要一个函数来标记节点,并修改此节点的和
void f(int o, int l, int r, int k) {
tag[o] += k;
sum[o] += k * (r - l + 1);
}
f函数可以做到,它可以保存标记,并区间修改
当然标记需要下传,修改子节点后,父节点也要改变:
void push_up(int x) {
sum[x] = sum[lc(x)] + sum[rc(x)];
}
push_up可以重新求父节点的值
void push_down(int o, int l, int r) {
int mid = l + r >> 1;
f(lc(o), l, mid, tag[o]);
f(rc(o), mid + 1, r, tag[o]);
tag[o] = 0;
}
push_down可以下传标记
而查询与更新只要递归查询,合并答案即可
【模板】线段树 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)。
分析2
多了个乘法,再开数组代码逻辑会乱一点,但开个结构体即可:
struct node{
int sum,mul,add;
};
不难想到,sum为区间和,mul为乘法懒标记,add为加法懒标记,对于一个区间x,可以存在子节点y,y应该接受懒标记:
y
s
u
m
=
(
y
s
u
m
∗
x
m
u
l
)
+
x
a
d
d
∗
(
y
r
−
y
l
+
1
)
y_{sum}=(y_{sum}*x_{mul})+x_{add}*(y_r-y_l+1)
ysum=(ysum∗xmul)+xadd∗(yr−yl+1)
修改y的懒标记:
y
m
u
l
=
y
m
u
l
∗
x
m
u
l
y_{mul}=y_{mul}*x_{mul}
ymul=ymul∗xmul
y
a
d
d
=
y
a
d
d
∗
x
m
u
l
+
x
a
d
d
y_{add}=y_{add}*x_{mul}+x_{add}
yadd=yadd∗xmul+xadd
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define rc(x) ((x<<1)|1)
#define lc(x) (x<<1)
const int M = 1e6;
const int N=1e18;
int a[M],n,m,mod;
void read(){
cin>>n;cin>>m>>mod;
for (int i=1;i<=n;i++) cin>>a[i];
}
struct node{
int sum,mul,add;
};
struct segment{
node seg[M<<2];
void push_up(int x){
seg[x].sum=seg[lc(x)].sum+seg[rc(x)].sum;
}
void build(int o,int l,int r){
seg[o].mul=1;seg[o].add=0;
if (l==r){
seg[o].sum=a[l];
return;
}
int mid=(l+r)>>1;
build(lc(o),l,mid);
build(rc(o),mid+1,r);
push_up(o);
}
void f(int o,int l,int r,int add,int mul){
seg[o].mul=(seg[o].mul*mul)%mod;
seg[o].add=(seg[o].add*mul)%mod;
seg[o].add=(seg[o].add+add)%mod;
seg[o].sum=(seg[o].sum*mul)%mod;
seg[o].sum=(seg[o].sum+(r-l+1)*add)%mod;
}
void push_down(int o,int l,int r){
int mid=(l+r)>>1;
f(lc(o),l,mid,seg[o].add,seg[o].mul);
f(rc(o),mid+1,r,seg[o].add,seg[o].mul);
seg[o].add=0;seg[o].mul=1;
}
void update(int ql,int qr,int add,int mul,int o=1,int l=1,int r=n){
if (ql<=l and r<=qr) {f(o,l,r,add,mul);return;}
int mid=(l+r)>>1;
push_down(o,l,r);
if(ql<=mid) update(ql,qr,add,mul,lc(o),l,mid);
if(qr>=1+mid)update(ql,qr,add,mul,rc(o),mid+1,r);
push_up(o);
}
int query(int ql,int qr,int o=1,int l=1,int r=n){
if (ql<=l and r<=qr) return seg[o].sum;
int mid=(l+r)>>1,ans=0;
push_down(o,l,r);
if (ql<=mid) ans=(ans+query(ql,qr,lc(o),l,mid))%mod;
if (mid+1<=qr) ans=(ans+query(ql,qr,rc(o),mid+1,r))%mod;
push_up(o);
return ans;
}
}T1;
void solve(){
int opt,x,y,k;
cin>>opt>>x>>y;
switch(opt){
case 1:{
cin>>k;
T1.update(x,y,0,k);
break;
}
case 2:{
cin>>k;
T1.update(x,y,k,1);
break;
}
default:cout<<T1.query(x,y)<<endl;
}
}
signed main(){
ios::sync_with_stdio(false);
read();
T1.build(1,1,n);
while(m--) solve();
return 0;
}
与线段树1大致相同,不再过多赘述