基础知识:
线段树是一颗二叉树搜索树,也是二叉平衡树。
- 根区间:[L,R]
- 左孩子区间:[L, (L+R)/2]
- 右孩子区间:[(L+R)/2+1, R]
- 叶子节点:L=R
树高logN 树上的操作都和树高有关系,例如:优先队列,堆等 时间复杂度O(longn)
用法:
- 区间更新
- 区间查询 例如区间最值查询(Rang Minimum/Maximum Query,RMQ)
树状数组擅长点更新和区间和查询、区间更新不擅长,没有加法关系树状数组就不适合。
线段树操作一.RMQ:
点更新:修改一个元素的值
区间查询:查询一个区间的最值(最大或者最小)
除了最后一层中间都是满二叉树,满二叉树可顺序存储,顺序存储可以直接定位。
tree[] k
/
2k 2k+1
n个节点需要空间是4*n
区间跟新打标记
点更新,区间查询,没有区间更新。
1 #include <iostream> 2 #include <cstring> 3 #include <algorithm> 4 5 using namespace std; 6 7 const int maxn = 1000005; 8 const int inf = 0x3f3f3f3f; 9 int n, a[maxn]; 10 11 struct node // 结点 12 { 13 int l, r, mx; // mx最值 14 }tree[maxn*4]; // 树结点存储数组 max<<2 15 16 void build(int k, int l, int r){ // 创建线段树,k表示存储下标,区间[l, r] 17 tree[k].l = l; 18 tree[k].r = r; 19 if(l==r){ 20 tree[k].mx = a[l]; 21 return; 22 } 23 int mid, lc, rc; 24 mid = (l+r)/2; 25 lc = k*2; 26 rc = k*2+1; 27 build(lc, l, mid); // 左孩子存储下标 28 build(rc, mid+1, r); // 右孩子存储下标 29 tree[k].mx = max(tree[lc].mx, tree[rc].mx); // 结点的最大值等于左右孩子的最大值 30 /* 这里只有递归创建完左右子树,才能的得到该节点的值mx。 */ 31 } 32 33 /* 单点更新 */ 34 void update(int k, int i, int v){ // 将a[i] 更新为v 35 if(tree[k].l==tree[k].r && tree[k].l==i){ //找到a[i] 36 tree[k].mx = v; 37 return ; 38 } 39 int mid, lc, rc; 40 mid = (tree[k].l + tree[k].r)/2; 41 lc = k*2; // 左孩子存储下标 42 rc = k*2+1; // 右孩子存储下标 43 if(i <= mid) 44 update(lc, i, v); 45 else 46 update(rc, i, v); 47 // 注意:这里是修改的单个节点的值 48 tree[k].mx = max(tree[lc].mx, tree[rc].mx); // 返回时修改更新最值 49 } 50 51 /* 区间最值查询 */ 52 int query(int k, int l, int r){ // 求区间[l..r]的最值 53 54 if(tree[k].l>=l && tree[k].r<=r) // 覆盖区间 找到该区间 55 return tree[k].mx; 56 57 // 继续分 58 int mid,lc,rc; 59 mid=(tree[k].l+tree[k].r)/2; // 划分点 60 lc=k*2; // 左孩子存储下标 61 rc=k*2+1;// 右孩子存储下标 62 int Max=-inf;// 注意,局部变量,全局变量不可以 63 64 if(l<=mid) 65 Max=max(Max,query(lc,l,r)); // 到左子查询 66 67 if(r>mid) 68 Max=max(Max,query(rc,l,r)); // 到右子树查询 69 70 return Max; 71 } 72 73 void print(int k) 74 { 75 if(tree[k].mx) 76 { 77 cout<<k<<" "<<tree[k].l<<" "<<tree[k].r<<" "<<tree[k].mx<<" "<<endl; 78 79 print(k<<1); // 递归查找 80 print((k<<1)+1); 81 } 82 } 83 84 int main() 85 { 86 // 10 5 3 7 2 12 1 6 4 8 15 87 int l,r; 88 int i,v; 89 cin>>n;//10 90 for(i=1;i<=n;i++) 91 cin>>a[i]; //5 3 7 2 12 1 6 4 8 15 92 build(1,1,n); // 创建线段树 93 print(1); 94 cout<<"输入查询最值的区间l r:"<<endl; 95 cin>>l>>r; 96 cout<<query(1,l,r)<<endl;// 求区间[l..r]的最值 97 cout<<"输入修改元素的下标和值i v:"<<endl; 98 cin>>i>>v; 99 update(1,i,v); // 将a[i]修改更新为v 100 cout<<"输入查询最值的区间l r:"<<endl; 101 cin>>l>>r; 102 cout<<query(1,l,r)<<endl; // 求z区间[l..r]的最值 103 return 0; 104 }
区间更新
懒标记 下传给子结点
1 #include <iostream> 2 #include <cstring> 3 #include <algorithm> 4 5 using namespace std; 6 7 const int maxn = 1000005; 8 const int inf = 0x3f3f3f3f; 9 int n, a[maxn]; 10 11 struct node // 结点 12 { 13 int l, r, mx, lz; // mx最值 14 }tree[maxn*4]; // 树结点存储数组 max<<2 15 16 void lazy(int k, int v){ 17 tree[k].mx = v; // 更新最值 18 tree[k].lz = v; //做懒标记 19 } 20 21 void pushdown(int k){ // 向下传递懒标记 22 lazy(2*k, tree[k].lz); // 传递给左孩子 23 lazy(2*k+1, tree[k].lz); // 传递给右孩子 24 tree[k].lz = -1; // 清楚自己的懒标记 先传给孩子的时候自己就没有懒标记了 25 } 26 27 void build(int k, int l, int r){ // 创建线段树,k表示存储下标,区间[l, r] 28 tree[k].l = l; 29 tree[k].r = r; 30 tree[k].lz = -1; // 初始化懒操作 31 if(l==r){ 32 tree[k].mx = a[l]; 33 return; 34 } 35 int mid, lc, rc; 36 mid = (l+r)/2; 37 lc = k*2; 38 rc = k*2+1; 39 build(lc, l, mid); // 左孩子存储下标 40 build(rc, mid+1, r); // 右孩子存储下标 41 tree[k].mx = max(tree[lc].mx, tree[rc].mx); // 结点的最大值等于左右孩子的最大值 42 /* 这里只有递归创建完左右子树,才能的得到该节点的值mx。 */ 43 } 44 45 /* 区间更新 */ 46 void update(int k, int l, int r, int v){ // 将区间[l,...,r]修改更新为v 47 if(tree[k].l>=l && tree[k].r<=r) // 找到该区间 48 return lazy(k, v); // 更新并做懒标记 49 50 if(tree[k].lz != -1) 51 pushdown(k); // 懒标记 下移 52 53 int mid, lc, rc; 54 55 mid = (tree[k].l + tree[k].r)/2; 56 lc = k*2; 57 rc = k*2+1; 58 if(l<=mid) 59 update(lc, l, r, v); 60 if(r>mid) 61 update(rc, l, r, v); 62 tree[k].mx = max(tree[lc].mx, tree[rc].mx); 63 } 64 65 /* 区间最值查询 */ 66 int query(int k, int l, int r){ // 求区间[l..r]的最值 67 68 if(tree[k].l>=l && tree[k].r<=r) // 覆盖区间 找到该区间 69 return tree[k].mx; 70 71 if(tree[k].lz != -1) 72 pushdown(k); 73 74 // 继续分 75 int mid,lc,rc; 76 mid=(tree[k].l+tree[k].r)/2; // 划分点 77 lc=k*2; // 左孩子存储下标 78 rc=k*2+1;// 右孩子存储下标 79 int Max=-inf;// 注意,局部变量,全局变量不可以 80 81 if(l<=mid) 82 Max=max(Max,query(lc,l,r)); // 到左子查询 83 84 if(r>mid) 85 Max=max(Max,query(rc,l,r)); // 到右子树查询 86 87 return Max; 88 } 89 90 void print(int k) 91 { 92 if(tree[k].mx) 93 { 94 cout<<k<<" "<<tree[k].l<<" "<<tree[k].r<<" "<<tree[k].mx<<" "<<endl; 95 96 print(k<<1); // 递归查找 97 print((k<<1)+1); 98 } 99 } 100 101 int main() 102 { 103 // 10 5 3 7 2 12 1 6 4 8 15 104 int l,r; 105 int i,v; 106 cin>>n;//10 107 for(i=1;i<=n;i++) 108 cin>>a[i]; //5 3 7 2 12 1 6 4 8 15 109 build(1,1,n); // 创建线段树 110 print(1); 111 cout<<"输入查询最值的区间l r:"<<endl; 112 cin>>l>>r; 113 cout<<query(1,l,r)<<endl;// 求区间[l..r]的最值 114 cout<<"输入更新的区间值l r v:"<<endl; 115 cin>>l>>r>>v; 116 update(1,l,r,v); // 将a[i]修改更新为v 117 cout<<"输入查询最值的区间l r:"<<endl; 118 cin>>l>>r; 119 cout<<query(1,l,r)<<endl; // 求z区间[l..r]的最值 120 return 0; 121 }