关于线段树的理解, 在自己需要时翻看。
什么是线段树?
- 线段树的本质是一棵二叉树,不同于其它二叉树,线段树的每一个节点记录的是一段区间的信息
线段树的功能?
- 更新点,查询区间
-
更新区间,查询点
- 更新区间,查询区间
线段树的优势?
一个长度为N的一维数组(a[1]~a[N])
我们每次对该数组有一些操作:
1、修改数组中某个元素的值
【1,5,4,1,6】---(a[2]=3)---> 【1,3,4,1,6】
2、询问数组中某段区间的最大值
【1,5,4,1,6】---(max(1,4)=?)---> 5
3、询问数组中某段区间的和
【1,5,4,1,6】---(sum(3,5)=?)---> 11
如果只有一次询问?
-
枚举相应区间内的元素,输出答案 O(N)
更多的询问?
-
Q次询问,O(NQ) That's too SLOW!
线段树——在O(log2N)的时间内完成每次操作 O(Qlog2N)
怎么去构造一个线段树?
- e.g. 对于长度为5的数组a[1]~a[5]
- 对于任一非叶子节点,若该区间为[L,R],则 左儿子为[L,(L+R)/2] 右儿子为[(L+R)/2+1,R]
-
我们用一个数组tree记录节点,且根节点的下标为1, 对于任一节点tree[k], 它的左儿子为tree[2*k] 它的右儿子为 tree[2*k+1]
- 这里我习惯用结构体数组, 感觉比较好理解.(也可以只使用一维数组记录最后叶结点的信息, 只不过在函数多加两个参数L,R即可)
-
struct Tree
-
{
-
int left,right; //区间的端点
-
int max,sum; //视题目要求而定
-
};
-
如图,就是我们要在线段树中存五个数字所实现的效果。其中, 每个父节点储存了左儿子和右儿子的信息,最后的叶节点储存了数组的信息。
线段树——代码实现(建树)
1 void pushup(int id)
2 {
3 tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
4 }
5 void build(int id, int l, int r)
6 {
7 tree[id].left=l;tree[id].right=r;
8 if(l==r)
9 tree[id].maxx=tree[id].sum=a[l];
10 else
11 {
12 int mid=(l+r)/2;
13 build(id<<1, l, mid);
14 build(id<<1|1, mid+1, r);
15 pushup(id);
16 }
17 }
线段树——代码实现(查询)
1 int query(int id, int l, int r)
2 {
3 if(l<=tree[id].left && tree[id].right<=r)
4 return tree[id].sum;
5 else
6 {
7 int ans;
8 int mid=(tree[id].left+tree[id].right)/2;
9 if(l<=mid) ans+=query(id<<1, l, r);
10 if(r>mid) ans+=query(id<<1|1, l, r);
11 return ans;
12 }
13 }
线段树——代码实现(单点更新)
1 void update(int id, int pos, int val)
2 {
3 if(tree[id].left==tree[id].right)
4 tree[id].sum=tree[id].maxx=val;
5 else
6 {
7 int mid=(tree[id].left+tree[id].right)/2;
8 if(pos<=mid) update(id<<1, pos, val);
9 else update(id<<1|1, pos, val);
10 pushup(id);
11 }
12 }
下面是上面的完全版代码
![](https://img-blog.csdnimg.cn/img_convert/8ba7702a4113756c15ddfc5f30182533.gif)
![](https://img-blog.csdnimg.cn/img_convert/34c0660d32e9187c64b2364fc59a9315.gif)
1 #include<iostream>
2 #include<algorithm>
3 using namespace std;
4 const int maxn=1e5+5;
5 int a[maxn];
6 struct note
7 {
8 int left, right, maxx, sum;
9 }tree[maxn*4];
10 void pushup(int id)
11 {
12 tree[id].sum=tree[id<<1].sum+tree[id<<1|1].sum;
13 }
14 void build(int id, int l, int r)
15 {
16 tree[id].left=l;tree[id].right=r;
17 if(l==r)
18 tree[id].maxx=tree[id].sum=a[l];
19 else
20 {
21 int mid=(l+r)/2;
22 build(id<<1, l, mid);
23 build(id<<1|1, mid+1, r);
24 pushup(id);
25 }
26 }
27 int query(int id, int l, int r)
28 {
29 if(l<=tree[id].left && tree[id].right<=r)
30 return tree[id].sum;
31 else
32 {
33 int ans;
34 int mid=(tree[id].left+tree[id].right)/2;
35 if(l<=mid) ans+=query(id<<1, l, r);
36 if(r>mid) ans+=query(id<<1|1, l, r);
37 return ans;
38 }
39 }
40 void update(int id, int pos, int val)
41 {
42 if(tree[id].left==tree[id].right)
43 tree[id].sum=tree[id].maxx=val;
44 else
45 {
46 int mid=(tree[id].left+tree[id].right)/2;
47 if(pos<=mid) update(id<<1, pos, val);
48 else update(id<<1|1, pos, val);
49 pushup(id);
50 }
51 }
52 int main()
53 {
54 int n, q, i;
55 cin>>n>>q;
56 for(i=1; i<=n; i++)
57 cin>>a[i];
58 build(1, 1, n);
59 while(q--)
60 {
61 int op;
62 cin>>op;
63 if(op==1)
64 {
65 int l, r;
66 cin>>l>>r;
67 cout<<query(1, l, r);
68 }
69 else
70 {
71 int pos, val;
72 cin>>pos>>val;
73 update(1, pos, val);
74 }
75
76 }
77
78 }
然后在贴一个线段树区间更新的代码, 也就是在数组中多加了一个懒标记,很好理解.
![](https://img-blog.csdnimg.cn/img_convert/10c52da36acbbc770a1db61f81d193cf.gif)
![](https://img-blog.csdnimg.cn/img_convert/689bf0a5dca9fe971fd5655eaaef9b93.gif)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int a[maxn];
struct note
{
int left,right,maxx,lazy;
void up(int val)
{
maxx+=val;
lazy+=val;
}
} tree[maxn*4];
void pushup(int id)
{
tree[id].maxx=max(tree[id<<1].maxx,tree[id<<1|1].maxx);
}
void pushdown(int id)
{
if(tree[id].lazy)
{
tree[id<<1].up(tree[id].lazy);
tree[id<<1|1].up(tree[id].lazy);
tree[id].lazy=0;
}
}
void build(int id,int l,int r)
{
tree[id].left=l;
tree[id].right=r;
if(l==r)
tree[id].maxx=a[l];
else
{
int mid=(l+r)/2;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
pushup(id);
}
}
int query(int id,int l,int r)
{
if(l<=tree[id].left&&tree[id].right<=r)
return tree[id].maxx;
pushdown(id);
int mid=(tree[id].left+tree[id].right)/2;
int ans=-0x3f3f3f3f;
if(l<=mid) ans=max(ans,query(id<<1,l,r));
if(r>mid) ans=max(ans,query(id<<1|1,l,r));
return ans;
}
void update(int id,int l,int r,int val)
{
if(l<=tree[id].left&&tree[id].right<=r)
{
tree[id].up(val);
return;
}
pushdown(id);
int mid=(tree[id].left+tree[id].right)/2;
if(l<=mid) update(id<<1,l,r,val);
if(r>mid) update(id<<1|1,l,r,val);
pushup(id);
}
int main()
{
int n,q;
scanf("%d%d",&n,&q);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
build(1,1,n);
while(q--)
{
int op;
cin>>op;
if(op==1)
{
int l, r;
cin>>l>>r;
cout<<query(1, l, r)<<endl;
}
else
{
int l, r, val;
cin>>l>>r>>val;
update(1, l, r, val);
}
}
}