线段树是一种二叉搜索树,用于处理与区间和查询更新有关的问题。线段树能在对数时间内完成单点查询、区间查询和单点更新等操作。相对于暴力搜索,有很明显的时间优化。
线段树与暴力的时间复杂的和空间复杂度的对比
时间复杂度
线段树
- 构建线段树:O(N),遍历原数组的每个元素来初始化线段树的叶子节点,然后自底向上合并信息。
- 单点查询:O(log N)。由于线段树是一棵平衡二叉树,从根节点到叶子节点的路径长度是对数级别的。
- 区间查询:O(log N)。与单点查询类似,虽然查询的是一个区间,但由于线段树的性质,可以在对数时间内完成。
- 单点更新:O(log N)。需要更新叶子节点,并可能需要更新其父节点和祖先节点。
- 区间更新:如果使用懒惰更新技巧,时间复杂度通常是O(log N)。如果不使用懒惰更新,则可能退化到O(N log N)。
暴力
- 单点查询:O(1)。直接访问数组中的元素。
- 区间查询:O(n),其中k是查询区间的长度。需要遍历区间内的每个元素。
- 单点更新:O(1)。直接更新数组中的元素。
- 区间更新:O(n),其中k是更新区间的长度。需要遍历区间内的每个元素并更新它们。
空间复杂度
线段树
- 线段树的空间复杂度通常是O(4N),因为它是一棵完全二叉树,每个节点通常存储两个子节点的索引以及对应的区间信息。
暴力
- 暴力操作的空间复杂度通常是O(N),即存储原数组的空间。它不需要额外的数据结构来支持查询和更新操作。
线段树的部分基本操作
1.建树
//建树
void build_tree(int arr[], int tree[], int node, int start, int end){
if(start == end){
tree[node] = arr[start];
return;
}
int mid = (start + end) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
build_tree(arr, tree, left_node, start, mid);
build_tree(arr, tree, right_node, mid+1, end);
tree[node] = tree[left_node] + tree[right_node];
}
2.单点更新
//单点更新
void update_tree(int arr[], int tree[], int node, int start, int end, int idx, int val){
if(start == end)
{
arr[idx] = val;
tree[node] = val;
return;
}
int mid = (start + end) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
if(idx >= start && idx <= mid){
update_tree(arr, tree, left_node, start, mid, idx, val);
}
else{
update_tree(arr, tree, right_node, mid + 1, end, idx, val);
}
tree[node] = tree[left_node] + tree[right_node];
}
3.区域查询
//区域查询
int query_tree(int arr[], int tree[], int node, int start, int end, int L, int R){
if(start > R || end < L){
return 0;
}
else if(L <= start && R >= end){
return tree[node];
}
int mid = (start + end) / 2;
int left_node = 2 * node + 1;
int right_node = 2 * node + 2;
int sum_left = query_tree(arr, tree, left_node, start, mid, L, R);
int sum_right = query_tree(arr, tree, right_node, mid+1, end, L, R);
return sum_left + sum_right;
}
线段树模版题
题目描述
原题点这里-->
线段树 1 - 洛谷 P3372 - Virtual Judge (vjudge.net)
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 k。
- 求出某区间每一个数的和。
Input
第一行包含两个整数 n,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 �m 行每行包含 33 或 44 个整数,表示一个操作,具体如下:
1 x y k
:将区间[x,y] 内每个数加上 k。2 x y
:输出区间[x,y] 内每个数的和。
Output
输出包含若干行整数,即为所有操作 2 的结果。
Sample 1
Inputcopy | Outputcopy |
---|---|
5 5 1 5 4 2 3 2 2 4 1 2 3 2 2 3 4 1 1 5 1 2 1 4 | 11 8 20 |
Hint
对于 30%30% 的数据:n≤8,m≤10。
对于 70%70% 的数据:n≤103,m≤104。
对于 100%100% 的数据:1≤n,m≤105。
保证任意时刻数列中所有元素的绝对值之和 ≤。
源代码
#include<stdio.h>
#define ll long long
#define MAX_LEN 100010
typedef struct{
int L, R;
ll val;
int lazy;
}Node;
ll arr[MAX_LEN];
Node tree[4 * MAX_LEN];
//建树
void build_tree(int node, int start, int end){
if(start == end){
tree[node].L = tree[node].R = start;
tree[node].val = arr[start];
return;
}
int mid = (start + end) / 2;
int left_node = 2 * node ;
int right_node = 2 * node + 1;
build_tree(left_node, start, mid);
build_tree(right_node, mid+1, end);
tree[node].val = tree[left_node].val + tree[right_node].val;
tree[node].L = start;
tree[node].R = end;
}
//向下传递lazy标记
void pushdown(int node)
{
if(tree[node].lazy)
{
int left_node = 2 * node;
int right_node = 2 * node + 1;
tree[left_node].lazy += tree[node].lazy;
tree[right_node].lazy += tree[node].lazy;
tree[left_node].val += (tree[left_node].R - tree[left_node].L + 1) * tree[node].lazy;
tree[right_node].val += (tree[right_node].R - tree[right_node].L + 1) * tree[node].lazy;
tree[node].lazy=0;
}
}
//区域更新
void update_tree(int node, int start, int end, int val){
if(tree[node].L >= start && tree[node].R <= end){
tree[node].val += val * (tree[node].R - tree[node].L + 1);
tree[node].lazy += val;
return ;
}
if(tree[node].L > end || tree[node].R < start)
return;
if(tree[node].lazy)
pushdown(node);
update_tree(2 * node, start, end, val);
update_tree(2 * node + 1, start, end, val);
tree[node].val = tree[2 * node].val + tree[2 * node + 1].val;
}
//区域查询
ll query_tree(int node, int start, int end){
if(tree[node].L >= start && tree[node].R <= end){
return tree[node].val;
}
if(tree[node].L > end || tree[node].R < start)
return 0;
if(tree[node].lazy)
pushdown(node);
return query_tree(2 * node, start, end) + query_tree(2 * node + 1, start, end);
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++){
scanf("%lld", &arr[i]);
}
build_tree(1, 1, n);
// for(int i = 1; i <= 9; i ++){
// printf("tree[%d] = %lld %d %d\n", i, tree[i].val,tree[i].L,tree[i].R);
// }
int k, x, y, val;
for(int i = 0; i < m; i ++){
scanf("%d", &k);
if(k == 1){
scanf("%d%d%d",&x, &y, &val);
update_tree(1, x, y, val);
}
else if(k == 2){
scanf("%d%d",&x, &y);
printf("%lld\n", query_tree(1, x, y));
}
// for(int i = 1; i <= 9; i ++){
// printf("tree[%d] = %lld %d %d\n", i, tree[i].val,tree[i].L,tree[i].R);
// }
}
return 0;
}