关于动态区间询问问题的通解
某些相关类型的算法既然都能存在,那么一定有它自己的优势
线段树最大的劣势:1. 常数消耗 2.空间消耗
ST 表的核心问题在于不能修改
假设现在有 个数字,如果只需要修改一个数字,在某些情况下我可能真的需要将整个 重新进行计算
假设现在需要修改的数字为
其实只需要修改包含了 的区间即可
首先包含 的区间只有可能出现在 之前
数量大概在 量级
为什么ST表修改很慢?因为涉及到 的区间太多了
核心问题:区间重叠面积很大
也就是说我们希望让区间尽可能的 独立
倍增思想带来的另一个思想:分治 -> 二分
// 以倍增的思想逼近答案
int now = 0;
for (int i = 30; i >= 0; --i){
if (check(now + (1 << i))){
now += 1 << i;
ans += value;
}
}
将所有区间对半切成两个小区间,进行存储
按照这种方式进行区间的分割,一个数字 最多影响 区间?

以这种形式构造区间,当需要修改 的时候,实际需要修改的就是 的所有祖先,也就是从 区间一直到根节点的路径上所有节点
而由于这种建树方式,我们的树最多只有 层,那么单次修改的复杂度就是
由于查询的区间必然是连续的
那么在树上的同一层中,必然最多只会选择两个节点
反证法:如果存在一层中出现选择了三个区间的情况,说明此时必然有一个区间在划分的时候,左右子区间都被选了,但是这种情况不如直接选择父区间
那么也就是说,一次询问最多可能包含的区间数是 ,所以一次查询的复杂度就是
树形结构一般怎么存?
二叉树 而言,有一种特殊的存法:把任意一棵二叉树当成完全二叉树进行存储,因为在完全二叉树中, 的孩子分别是 和 ,所以此时可以直接用数组进行存储, 表示 节点的信息请问空间消耗?
int tree[4 * maxn]
2^0
2^1
2^2
2^3
2^4
...
2^(logn + 1)
求和 = 2^(logn + 2) - 1
= 2^2 * 2^(logn) - 1
= 4 * 2^logn - 1
= 4 * n - 1
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
const int INF = 0x3f3f3f3f;
int a[maxn], n, m;
int sumv[4 * maxn];
int minv[4 * maxn];
int maxv[4 * maxn];
// 当前区间编号为 id,对应区间 [l,r]
void build(int id, int l, int r){
//递归到叶子结点结束
if (l == r){
sumv[id] = a[l];
minv[id] = a[l];
maxv[id] = a[l];
return;
}
//大区间分成两个区间
int mid = (l + r) / 2;
//拆分左右两个子区间
build(id * 2, l, mid); //左区间
build(id * 2 + 1, mid + 1, r);//右区间
sumv[id] = sumv[id * 2] + sumv[id * 2 + 1];
minv[id] = min(minv[id * 2], minv[id * 2 + 1]);
maxv[id] = max(maxv[id * 2], maxv[id * 2 + 1]);
}
//当前区间编号为 id,对应区间 [l,r]
//当前查询区间为 [x,y]
int query(int id, int l, int r, int x, int y){
//如果当前区间整个被需要查询的区间包含
//则直接返回区间信息
if (x <= l && r <= y){
return minv[id];
return sumv[id];
return maxv[id];
}
//如果当前区间没有被包含
//则需要考虑左右子区间是否需要查询
int mid = (l + r) / 2;
int ans = INF;
// 左区间 [l, mid]
if (x <= mid){
ans = min(ans, query(id * 2, l, mid, x, y));
}
// 右区间 [mid + 1, r]
if (y >= mid + 1){
ans = min(ans, query(id * 2 + 1, mid + 1, r, x, y));
}
return ans;
}
// 当前编号 id,对应区间 [l,r]
// 需要修改节点 x,修改值为 v
void update(int id, int l, int r, int x, int v){
//更新到叶子结点结束
if (l == r){
a[l] = v;
sumv[id] = v;
minv[id] = v;
maxv[id] = v;
return ;
}
//还没到叶子则分左右子区间更新
//挑其中一个更新即可
int mid = (l + r) / 2;
if (x <= mid){
update(id * 2, l, mid, x, v);
} else {
update(id * 2 + 1, mid + 1, r, x, v);
}
sumv[id] = sumv[id * 2] + sumv[id * 2 + 1];
minv[id] = min(minv[id * 2], minv[id * 2 + 1]);
maxv[id] = max(maxv[id * 2], maxv[id * 2 + 1]);
}
int main(){
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i){
scanf("%d", &a[i]);
}
build(1, 1, n);
while (m--){
int op, x, y;
scanf("%d%d%d", &op, &x, &y);
if (op == 1){
printf("%d ", query(1, 1, n, x, y));
} else {
update(1, 1, n, x, y);
}
}
return 0;
}