线段树入门(线段懵逼树)
线段树上懵逼果,线段树下我和我,线段树上找bug,掉发多又多-----------题记
辣鸡张当时学习的博客(如果学到了新的东西或者说有新的理解后期再更新)
xy 写得这篇也挺好的,商业
胡
吹
\sout{胡吹}
胡吹互吹链接???
∙
\bullet
∙线段树常用来处理区间和、区间最大值、区间最小值问题,但是不仅局限于处理区间问题。
∙
\bullet
∙什摸士线段树:线段树是一棵二叉搜索树。每个结点存储的是一段区间的最大值、最小值或者区间内元素的和。每个结点最多有两个孩子(可以有一个或者两个甚至是个没有孩子的单身大汉)。如果父亲结点的标号是
r
o
o
t
root
root,辣么它的左孩子的标号解就是
r
o
o
t
∗
2
root*2
root∗2,右孩子的标号就是
r
o
o
t
∗
2
+
1
root*2+1
root∗2+1。以下面的线段懵逼树为例:1号结点就存储的是【1,5】区间的和。
∙
\bullet
∙问题引入:点此打开自闭页给你一个
F
u
c
k
i
n
g
\sout{Fucking}
Fucking序列
a
1
.
.
.
.
.
.
a
n
a_1......a_n
a1......an再来
q
q
q次骚操作。
每次操作有两种:“C a b c” means adding c to each of
A
a
,
A
a
+
1
,
.
.
.
,
A
b
.
−
10000
≤
c
≤
10000.
A_a, A_{a+1}, ... , A_b. -10000 ≤ c ≤ 10000.
Aa,Aa+1,...,Ab.−10000≤c≤10000.
“Q a b” means querying the sum of
A
a
,
A
a
+
1
,
.
.
.
,
A
b
.
A_a, A_{a+1}, ... , A_b.
Aa,Aa+1,...,Ab. So, you need to answer each ‘Q’.
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15
懵逼少年找了许久bug,终于解决了这个题
∙
\bullet
∙模板代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int maxn = 1e5 + 10;
LL num[maxn];
LL n,q;
struct node{LL sum,lazy;}tree[maxn*6];
void update(LL root){
tree[root].sum=tree[root * 2].sum+tree[root * 2 + 1].sum;
return ;
}
void build(LL root,LL l,LL r){///建树
tree[root].lazy = 0;///初始化
tree[root].sum = 0;///初始化
if(l == r){///如果左端点=又端点就说明走到了叶子节点,此时的sum肯定是叶子节点的值
tree[root].sum = num[l];
return ;
}
LL m = (l + r) / 2;
build(root*2,l,m);
build(root*2+1,m+1,r);
update(root);
}
void pushdown(LL root,LL l,LL r){
if(tree[root].lazy == 0)return ;
LL m= (l + r) / 2;
LL left = root * 2;
LL right = root * 2 + 1;
tree[left].lazy += tree[root].lazy;
tree[right].lazy += tree[root].lazy;
tree[left].sum += tree[root].lazy * (m-l+1);
tree[right].sum += tree[root].lazy * (r-m);
tree[root].lazy = 0;
}
void change(LL root,LL l,LL r,LL ql,LL qr,LL val){
if(l >= ql && r <= qr){
tree[root].lazy += val;
tree[root].sum += (r-l+1) * val;
return ;
}
pushdown(root,l,r);
LL m = (l + r) / 2;
if(ql <= m)change(root*2,l,m,ql,qr,val);
if(qr > m)change(root*2+1,m+1,r,ql,qr,val);
update(root);
}
LL query(LL root,LL l,LL r,LL ql,LL qr){
if(l >= ql && r <= qr){
return tree[root].sum;
}
pushdown(root,l,r);
LL m = (l + r) / 2;
LL sum = 0;
if(ql<=m)sum += query(root*2,l,m,ql,qr);
if(qr>m)sum += query(root*2+1,m+1,r,ql,qr);
return sum;
}
void input(){
for(int i = 1;i <= n;i++)
scanf("%lld",&num[i]);
}
int main()
{
while(scanf("%lld%lld",&n,&q)!=EOF){
input();
build(1,1,n);
while(q--){
char str[10]={'\0'};
scanf("%s",str);
if(strcmp(str,"C") == 0){
LL a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
change(1,1,n,a,b,c);
}
if(strcmp(str,"Q") == 0){
LL a,b;
scanf("%lld%lld",&a,&b);
LL ans = query(1,1,n,a,b);
printf("%lld\n",ans);
}
}
}
return 0;
}
∙ \bullet ∙解释一下 b u i l d ( ) build() build()、 u p d a t e ( ) update() update()函数:
void update(LL root){
tree[root].sum=tree[root * 2].sum+tree[root * 2 + 1].sum;
return ;
}
void build(LL root,LL l,LL r){///建树
tree[root].lazy = 0;///初始化
tree[root].sum = 0;///初始化
if(l == r){///如果左端点=又端点就说明走到了叶子节点,此时的sum肯定是叶子节点的值
tree[root].sum = num[l];
return ;
}
LL m = (l + r) / 2;
build(root*2,l,m);
build(root*2+1,m+1,r);
update(root);
}
这是一个递归建树的过程,下面就来模拟一下回溯的过程,现在就到了递归的出口,就是左端点=右端点,此时就走到了叶子节点,此时的sum肯定是叶子节点的值,黄色代表已经更新过值的结点,蓝色代表更新完叶子节点后回溯到上一层的结点:
然后就要通过
u
p
d
a
t
e
(
)
update()
update()函数更新他们的爸爸4号节点:
更新完爸爸结点后又要回溯:
此时又要去更新2号节点的右儿子5号结点:
5号结点更新完后又回溯到了2号结点
然后就要通过
u
p
d
a
t
e
(
)
update()
update()函数更新2号结点:
之后的过程就是这样,直至整棵树构造好。
∙ \bullet ∙解释一下 p u s h d o w n ( ) pushdown() pushdown()和 c h a n g e ( ) change() change()函数:
void pushdown(LL root,LL l,LL r){
if(tree[root].lazy == 0)return ;
LL m= (l + r) / 2;
LL left = root * 2;
LL right = root * 2 + 1;
tree[left].lazy += tree[root].lazy;
tree[right].lazy += tree[root].lazy;
tree[left].sum += tree[root].lazy * (m-l+1);
tree[right].sum += tree[root].lazy * (r-m);
tree[root].lazy = 0;
}
void change(LL root,LL l,LL r,LL ql,LL qr,LL val){
if(l >= ql && r <= qr){
tree[root].lazy += val;
tree[root].sum += (r-l+1) * val;
return ;
}
pushdown(root,l,r);
LL m = (l + r) / 2;
if(ql <= m)change(root*2,l,m,ql,qr,val);
if(qr > m)change(root*2+1,m+1,r,ql,qr,val);
update(root);
}
如果要给一段连续的区间加上某个值 c c c(发零花钱哈哈哈),当然可以每次都找到叶子节点然后将它们更新就可以了,但是这样子太费时间了(这样就叫做单点更新,但是我还是喜欢单点更新,因为代码少)。不过有更高级的做法噻:如果某个结点所管辖的区间在你要更新的区间内(也就是说你要更新的区间包含某个结点所管辖的区间)那就直接让这个结点的sum值乘以区间的长度再乘以 c c c就行了撒,那就出大问题了啊,它就十分的贪污了啊,那他的孩子结点呢,就不管了吗。为了解决这个问题,我们引入了lazy标记,就是结构体里的lazy。偷了懒之后,就把lazy的值加上它贪污的值(为甚麽要“加”,直接赋值不就好了吗,不不不,因为它有可能多次贪污哈哈)。
在加上lazy之后,为甚麽有个 p u s h d o w n ( ) pushdown() pushdown()函数呢,这个函数的作用就是将爸爸的贪污值传递给它的儿子,让他的儿子的sum加上贪污值,并且给它的儿子也标记上lazy值,然后再把自己的lazy值删去(不仅贪污,还让儿子背锅)。你可能会问这不就多此一举吗,不不不。如果题目有多次给一段区间加上某个值,假如有一次给2号结点标记了lazy值,然后下一次又要给4号结点标记lazy值,如果没有 p u s h d o w n ( ) pushdown() pushdown()函数,是不是2号结点的孩子4号就少了钱,那他的爸爸就真的贪污了。如果有 p u s h d o w n ( ) pushdown() pushdown()函数,并且 p u s h d o w n ( ) pushdown() pushdown()函数还是加在递归走左右两个孩子的语句之前,那么在给4号结点标记lazy值之前就把它的爸爸2号的lazy值传递到4号了,这样4号的钱就不会少了。这个 p u s h d o w n ( ) pushdown() pushdown()函数就是预防贪污。
注意递归走完左右两个孩子之后还要 u p d a t e ( ) update() update()一下。
∙ \bullet ∙解释一下 q u e r y ( ) query() query()函数:
LL query(LL root,LL l,LL r,LL ql,LL qr){
if(l >= ql && r <= qr){
return tree[root].sum;
}
pushdown(root,l,r);
LL m = (l + r) / 2;
LL sum = 0;
if(ql<=m)sum += query(root*2,l,m,ql,qr);
if(qr>m)sum += query(root*2+1,m+1,r,ql,qr);
return sum;
}
q u e r y ( ) query() query()函数函数里的 p u s h d o w n ( ) pushdown() pushdown()函数跟上一个是一样的功能,就不多说了。递归出口就是如果某个结点所管辖的区间在你要更新的区间内(也就是说你要更新的区间包含某个结点所管辖的区间),就直接返回sum值就行了。
∙ \bullet ∙再贴一个求区间最大值的代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<vector>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long LL;
const int inf=0x3f3f3f3f;
const int maxn = 2e5 + 10;
LL num[maxn];
LL n,q;
struct node{LL val;}tree[maxn*4];
void update(LL root){
tree[root].val=max(tree[root*2].val,tree[root*2+1].val);
return ;
}
void build(LL root,LL l,LL r){
tree[root].val=-inf;
if(l==r){
tree[root].val=num[l];
return ;
}
LL m = ( l + r ) / 2;
build(root*2,l,m);
build(root*2+1,m+1,r);
update(root);
}
void change(LL root,LL l,LL r,LL pos,LL val){
if(l == r){
tree[root].val = val;
return ;
}
int m = ( l + r ) / 2;
if(pos <= m)change(root*2,l,m,pos,val);
if(pos > m)change(root*2+1,m+1,r,pos,val);
update(root);
}
int query(LL root,LL l,LL r,LL ql,LL qr){
if(l>=ql&&r<=qr){
return tree[root].val;
}
int m = (l + r) / 2;
int ans = -inf;
if(ql <= m)ans = max(ans,query(root*2,l,m,ql,qr));
if(qr > m)ans = max(ans,query(root*2+1,m+1,r,ql,qr));
return ans;
}
void input(){
for(int i=1;i<=n;i++){
scanf("%lld",&num[i]);
}
}
int main()
{
while(scanf("%lld%lld",&n,&q)!=EOF){
input();
build(1,1,n);
while(q--){
char str[10] = {'\0'};
scanf("%s",str);
if(strcmp(str,"Q") == 0){
LL a,b;
scanf("%lld%lld",&a,&b);
LL ans=query(1,1,n,a,b);
printf("%lld\n",ans);
}
if(strcmp(str,"U") == 0){
LL a,b;
scanf("%lld%lld",&a,&b);
change(1,1,n,a,b);
}
}
}
return 0;
}