关于区间求和求最值问题
如果有一串数组,现在要把其中一段区间的所有数加起来,该怎么办?如果修改了其中的某个值之后再把它们加起来,又该怎么办?
- 直接加起来。这样修改值所需时间是O(1),但是求和需要O(n),如果数组一大,就有点慢了。
- 做一个前缀数组,数组里第n个值就是原来数组前n个数的和。经过预处理后,求和所需时间是O(1),但修改值后再求和所需时间就变成了O(n),我觉得这和第一种方法一样鸡肋,还更复杂了。
- 线段树,也就是我今天学的东西,求和与修改值所需时间之间做了平衡。建树需要O(n),使用时为O(logn)。
- ST表,没学。
求最值的思想是一样的,反正线段树挺好。
关于线段树
我的理解
假设数组长度为n,将数组转换成一棵树,树的根节点所储存的树就是第0到第n-1个元素的和,其左右枝分别是第0到第(0+n-1)/2个元素的和与第1+(0+n-1)/2到第n个元素的和,以此类推,最后一定会算成两片叶子(两个元素)的和。
之后求区间和,就一定能分成一或两个树中元素的和,只需要找到这两个元素即可。而修改值也只需要找到相对应的那一条枝干即可。
我需要做的事
- 建立能将数组转换成一棵树并求和的函数
- 建立能修改数组以及树中对应的值的函数
- 建立修改树叶后能重新求和的函数
- . 建立能求区间和的函数
- 种植除了能求区间和以外(区间最值)的线段树
orz…再说再说
关于代码
我是看B站up主某月点灯笼学的线段树,码完之后改正美化也是照着来的,所以可以说代码是完全一样的。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//将正常的数组转为一棵树
void build_tree(int array[], int tree[],int root,int op, int ed)
{
if (op == ed)
tree[root] = array[op]; //递归尾部,给树叶赋值
else {
int mid = (op + ed) / 2;
int l_son = root * 2 + 1;
int r_son = root * 2 + 2;
build_tree(array, tree, l_son, op, mid); //如果不是叶子,就递归
build_tree(array, tree, r_son, mid + 1, ed);
tree[root] = tree[l_son] + tree[r_son]; //树枝等于两片叶子相加
}
}
//修改树叶之后,整条树枝都要修改
void re_tree(int tree[], int root)
{
tree[root] = tree[root * 2 + 1] + tree[root * 2 + 2];
if (root == 0)
return;
re_tree(tree, (root - 1) / 2);
}
void change_tree(int array[], int tree[], int root, int op, int ed, int leaf, int num)
{
if (op == ed) //找到之后修改叶子
{
tree[root] = num;
array[op] = num;
re_tree(tree, (root-1) / 2);
}
else //查找树上对应的叶子
{
int mid = (op + ed) / 2;
int l_son = root * 2 + 1;
int r_son = root * 2 + 2;
if (op <= leaf && leaf <= mid)
change_tree(array, tree, l_son, op, mid, leaf, num);
if (mid < leaf && leaf <= ed)
change_tree(array, tree, r_son, mid + 1, ed, leaf, num);
}
}
//区间的树叶求和
int sum_tree(int array[], int tree[], int root, int op, int ed, int l, int r)
{
if (r < op || ed < l)
return 0;
else if (l <= op && ed <= r)
return tree[root];
else if (op == ed)
return tree[root];
else {
int mid = (op + ed) / 2;
int l_son = root * 2 + 1;
int r_son = root * 2 + 2;
int l_sum = sum_tree(array, tree, l_son, op, mid, l, r);
int r_sum = sum_tree(array, tree, r_son, mid + 1, ed, l, r);
return l_sum + r_sum;
}
}
int main()
{
int i, n, m, * arr, * tree;
scanf("%d", &n);
arr = (int*)malloc(sizeof(int) * n); //开拓数组
for (m = 1; m <= n * 2 - 1; m *= 2); //树所需要的容量
tree = (int*)malloc(sizeof(int) * --m); //种树
memset(arr, 0, sizeof(int) * n); //初始化数组
for (i = 0; i < n; i++)
scanf("%d", &arr[i]); //输入数组
memset(tree, 0, sizeof(int) * m); //初始化树
build_tree(arr, tree, 0, 0, n - 1); //开始构建树
for (i = 0; i < m; i++)
printf("%d ", tree[i]); printf("\n"); //测试转换的树
int leaf, num; //要修改的叶子
scanf("%d%d", &leaf, &num); //输入替换的数
change_tree(arr, tree, 0, 0, n - 1, leaf-1, num); //开始改变叶子
for (i = 0; i < m; i++)
printf("%d ", tree[i]); printf("\n"); //测试改变的树
int l, r; //要求和的区间
scanf("%d%d", &l, &r); //输入求和区间
printf("%d", sum_tree(arr, tree, 0, 0, n - 1, l-1, r-1)); //测试区间求和
free(tree); //砍树
free(arr);
return 0;
}