线段树
一:基本操作:
线段树基于分治思想将区间及其最值进行存储,构成一颗二叉树,每个节点包含区间【L,R】 以及区间的最值,通过递归实现操作。
由于是二分,线段树是一颗平衡二叉树树高为O(logn)
1:存储
定义结构体:
1 #define N 100010
2 struct shu{
3 int l,r,mx;
4 }tree[4*N];//主要将区间进行存储
算法步骤:
(1)若是叶子节点(l==r),则节点的最值就是对应的a[i]
(2)非叶子节点,则递归创建左右子树
(3)节点的区间的最值等于该节点左右子树的最大值
1 void build(int k,int l,int r){//节点下标从k开始
2 tree[k].l=l;
3 tree[k].r=r;
4 if(l==r){
5 tree[k].mx=a[l];
6 return ;
7 }
8 int mid=(l+r)/2,lc,rc;
9 lc=2*k,rc=2*k+1;
10 build(lc,l,mid);
11 build(rc,mid+1,r);
12 tree[k].mx=max(tree[lc].mx,tree[rc].mx);
13 }
2:点更新
点更新指修改一个元素的值,例如将a[i]修改为v。采用递归进行点更新,算法步骤如下。
(1)若是叶子节点,满足l=r且l=i,则修改该节点的最值为v。
(2)若是非叶子节点,则判断是在左子树中更新还是在右子树中更新。
(3)返回时更新节点的最值。
void update(int k,int i,int v){
if(tree[k].l==tree[k].r&&tree[k].l==i){//找到a[i]
tree[k].mx=v;
return ;
}
int mid=(tree[k].l+tree[k].r)/2,lc,rc;
lc=2*k,rc=2*k+1;
if(i<=mid)
update(lc,i,v);
else
update(rc,i,v);
tree[k].mx=max(tree[lc].mx,tree[rc].mx);
}
3:区间查询
同样采用递归操作
(1)若节点所在的区间被查询区间[l,r]覆盖,则返回该节点的最值。
(2)判断是在左子树中查询,还是在右子树中查询。
(3)返回最值。
1 int query(int k,int l,int r){
2 if(tree[k].l>=l&&tree[k].r<=r){
3 return tree[k].mx;
4 }
5 int mid=(tree[k].l+tree[k].r)/2,lc,rc;
6 lc=2*k,rc=2*k+1;
7 int maxn=-inf;
8 if(l<=mid){
9 maxn=max(query(lc,l,r),maxn);
10 }
11 if(r>mid){
12 maxn=max(query(rc,l,r),maxn);//注意可能会两边同时查询
13 }
14 return maxn;
15 }
总代码:(简易)
1 #include<bits/stdc++.h>
2 using namespace std;
3 #define N 100010
4 #define inf 0x3f3f3f3f
5 struct shu{
6 int l,r,mx;
7 }tree[4*N];
8 int a[N];
9 void build(int k,int l,int r){
10 tree[k].l=l;
11 tree[k].r=r;
12 if(l==r){
13 tree[k].mx=a[l];
14 return ;
15 }
16 int mid=(l+r)/2,lc,rc;
17 lc=2*k,rc=2*k+1;
18 build(lc,l,mid);
19 build(rc,mid+1,r);
20 tree[k].mx=max(tree[lc].mx,tree[rc].mx);
21 }
22 void update(int k,int i,int v){
23 if(tree[k].l==tree[k].r&&tree[k].l==i){
24 tree[k].mx=v;
25 return ;
26 }
27 int mid=(tree[k].l+tree[k].r)/2,lc,rc;
28 lc=2*k,rc=2*k+1;
29 if(i<=mid)
30 update(lc,i,v);
31 else
32 update(rc,i,v);
33 tree[k].mx=max(tree[lc].mx,tree[rc].mx);
34 }
35 int query(int k,int l,int r){
36 if(tree[k].l>=l&&tree[k].r<=r){
37 return tree[k].mx;
38 }
39 int mid=(tree[k].l+tree[k].r)/2,lc,rc;
40 lc=2*k,rc=2*k+1;
41 int maxn=-inf;
42 if(l<=mid){
43 maxn=max(query(lc,l,r),maxn);
44 }
45 if(r>mid){
46 maxn=max(query(rc,l,r),maxn);
47 }
48 return maxn;
49 }
50 int main(){
51 int n;
52 cin>>n;
53 for(int i=1;i<=n;i++)
54 scanf("%d",&a[i]);
55 build(1,1,n);
56 int m,v,x;
57 cin>>m;//更新节点,m次操作
58 for(int i=1;i<=m;i++){
59 scanf("%d%d",&x,&v);
60 update(1,x,v);
61 }
62 int a,b,q;//查询a到b的最值
63 cin>>q;
64 for(int i=1;i<=q;i++){
65 scanf("%d%d",&a,&b);
66 cout<<query(1,a,b)<<endl;;
67 }
68 return 0;
69 }
二:懒操作
懒操作大概理解就是:“到这里就行了,其他的以后遇到再说吧”(可以降低时间复杂度)
1:区间更新 例:将[l,r]区间内的元素都改为v
(1)若当前节点的区间被查询区间[l,r]覆盖,则仅对该节点进行更新并做懒标记,表示该节点已被更新,对该节点的子节点暂不更新。
(2)判断是在左子树中查询还是在右子树中查询。在查询过程中,若当前节点带有懒标记,则将懒标记下传给子节点(将当前节点的懒标记清除,将子节点更新并做懒标记),继续查询。
(3)在返回时更新最值。
1 struct shu{
2 int l,r,mx,lz;
3 }tree[4*N];
4 void lazy(int k,int v){
5 tree[k].mx=v;//更新最值
6 tree[k].lz=v;
7 }
8 void pushdown(int k){
9 lazy(2*k,tree[k].lz);
10 lazy(2*k+1,tree[k].lz);
11 tree[k].lz=-1;//注意下传以后要清除
12 }
13 void update(int k,int l,int r,int v){
14 if(tree[k].l>=l&&tree[k].r<=r)
15 return lazy(k,v);//更新之后返回
16 if(tree[k].lz!=-1)
17 pushdown(k);//因为要更新了,所以必须行动
18 int mid=(tree[k].l+tree[k].r)/2,lc,rc;
19 lc=2*k,rc=2*k+1;
20 if(l<=mid)
21 update(lc,l,r,v);
22 if(r>mid)
23 update(rc,l,r,v);
24 tree[k].mx=max(tree[lc].mx,tree[rc].mx);
25 }
2:区间查询
注意查询过程中遇到懒标记就要下传,然后继续查询:
1 int query(int k,int l,int r){
2 if(tree[k].l>=l&&tree[k].r<=r){
3 return tree[k].mx;
4 }
5 if(tree[k].lz!=-1){
6 pushdown(k);
7 }
8 int mid=(tree[k].l+tree[k].r)/2,lc,rc;
9 lc=2*k,rc=2*k+1;
10 int maxn=-inf;
11 if(l<=mid){
12 maxn=max(query(lc,l,r),maxn);
13 }
14 if(r>mid){
15 maxn=max(query(rc,l,r),maxn);
16 }
17 return maxn;
18 }
分析:
采用二分分治思想,单点更新,区间更新,区间查询均可在O(logn)时间完成
PS.