线段树&线段树模版题AC代码

 线段树是一种二叉搜索树,用于处理与区间和查询更新有关的问题。线段树能在对数时间内完成单点查询、区间查询和单点更新等操作。相对于暴力搜索,有很明显的时间优化。

线段树与暴力的时间复杂的和空间复杂度的对比

 时间复杂度

线段树

  • 构建线段树: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)

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k。
  2. 求出某区间每一个数的和。

Input

第一行包含两个整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 �m 行每行包含 33 或 44 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间[x,y] 内每个数加上 k。
  2. 2 x y:输出区间[x,y] 内每个数的和。

Output

输出包含若干行整数,即为所有操作 2 的结果。

Sample 1

InputcopyOutputcopy
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。

保证任意时刻数列中所有元素的绝对值之和 ≤10^{18}

 源代码

#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;
}
		

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值