update
This commit is contained in:
parent
8282453b9f
commit
9150dd04d9
@ -108,3 +108,7 @@ ll ksm(ll a,ll b,ll M){
|
||||
|
||||
## Day9
|
||||
### 字典树与异或极值
|
||||
|
||||
## Day11
|
||||
[树状数组MD](./day11/FenwickTree/FenwickTree.md)
|
||||
[树状数组PDF](./day11/FenwickTree/FenwickTree.pdf)
|
@ -219,3 +219,235 @@ int sum = query(6, bit) - query(2, bit);
|
||||
### 总结
|
||||
|
||||
我们需要使用 `l - 1` 是因为 `query(r, bit)` 计算的是从 `1` 到 `r` 的前缀和,而 `query(l - 1, bit)` 则是计算从 `1` 到 `l-1` 的前缀和。通过计算差值,我们得到了 `[l, r]` 区间的和。
|
||||
|
||||
在树状数组中,区间修改(即对数组中一段连续的区间进行统一的增加或减少)通常是通过一些技巧来实现的。由于传统的树状数组主要支持单点更新和前缀和查询,要支持区间修改,我们可以采用一些特殊的设计思路。
|
||||
|
||||
### 1. 传统树状数组的局限性
|
||||
|
||||
通常,树状数组适合以下操作:
|
||||
- **单点更新**:即在数组的某个位置 `i` 上加上或减去一个值 `delta`。
|
||||
- **前缀和查询**:查询从数组开始到某个位置 `i` 的所有元素的和。
|
||||
|
||||
然而,直接使用树状数组并不容易支持区间修改(例如同时对 `[l, r]` 区间内的所有元素增加 `delta`)。
|
||||
|
||||
### 2. 双树状数组解决方案
|
||||
|
||||
为了实现区间修改,可以使用**两个树状数组**来分别维护差分数组。我们可以通过这两个树状数组来支持区间的加减操作。
|
||||
|
||||
#### 思路
|
||||
|
||||
考虑一个数组 `arr`,我们希望对其进行区间修改(比如对 `[l, r]` 区间内的所有元素增加一个值 `delta`)。我们可以引入两个树状数组 `B1` 和 `B2` 来分别维护 `arr` 的差分信息:
|
||||
- 树状数组 `B1` 用于处理与位置 `i` 成正比的影响。
|
||||
- 树状数组 `B2` 用于处理常数影响。
|
||||
|
||||
#### 如何更新和查询
|
||||
|
||||
1. **区间更新**:对 `[l, r]` 区间增加 `delta`,分两步来做:
|
||||
- 更新 `B1` 数组:`B1[l] += delta`,`B1[r+1] -= delta`。
|
||||
- 更新 `B2` 数组:`B2[l] += delta * (l-1)`,`B2[r+1] -= delta * r`。
|
||||
|
||||
具体实现:
|
||||
|
||||
```cpp
|
||||
void range_update(int l, int r, int delta, vector<int>& B1, vector<int>& B2, int n) {
|
||||
// 更新 B1
|
||||
update(l, delta, n, B1);
|
||||
update(r + 1, -delta, n, B1);
|
||||
// 更新 B2
|
||||
update(l, delta * (l - 1), n, B2);
|
||||
update(r + 1, -delta * r, n, B2);
|
||||
}
|
||||
```
|
||||
|
||||
2. **前缀和查询**:查询前缀和 `sum(1...i)` 可以通过以下方式获得:
|
||||
- `sum(1...i) = query(i, B1) * i - query(i, B2)`
|
||||
|
||||
具体实现:
|
||||
|
||||
```cpp
|
||||
int prefix_sum(int i, vector<int>& B1, vector<int>& B2) {
|
||||
return query(i, B1) * i - query(i, B2);
|
||||
}
|
||||
```
|
||||
|
||||
3. **区间查询**:查询区间 `[l, r]` 的和:
|
||||
- `sum(l...r) = prefix_sum(r) - prefix_sum(l-1)`
|
||||
|
||||
具体实现:
|
||||
|
||||
```cpp
|
||||
int range_query(int l, int r, vector<int>& B1, vector<int>& B2) {
|
||||
return prefix_sum(r, B1, B2) - prefix_sum(l - 1, B1, B2);
|
||||
}
|
||||
```
|
||||
|
||||
#### 代码示例
|
||||
|
||||
以下是完整的代码示例,包含区间修改和区间查询的实现:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// 单点更新函数
|
||||
void update(int i, int delta, int n, vector<int>& bit) {
|
||||
while (i <= n) {
|
||||
bit[i] += delta;
|
||||
i += i & (-i);
|
||||
}
|
||||
}
|
||||
|
||||
// 单点查询函数
|
||||
int query(int i, vector<int>& bit) {
|
||||
int sum = 0;
|
||||
while (i > 0) {
|
||||
sum += bit[i];
|
||||
i -= i & (-i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
// 前缀和查询函数
|
||||
int prefix_sum(int i, vector<int>& B1, vector<int>& B2) {
|
||||
return query(i, B1) * i - query(i, B2);
|
||||
}
|
||||
|
||||
// 区间和查询函数
|
||||
int range_query(int l, int r, vector<int>& B1, vector<int>& B2) {
|
||||
return prefix_sum(r, B1, B2) - prefix_sum(l - 1, B1, B2);
|
||||
}
|
||||
|
||||
// 区间更新函数
|
||||
void range_update(int l, int r, int delta, vector<int>& B1, vector<int>& B2, int n) {
|
||||
update(l, delta, n, B1);
|
||||
update(r + 1, -delta, n, B1);
|
||||
update(l, delta * (l - 1), n, B2);
|
||||
update(r + 1, -delta * r, n, B2);
|
||||
}
|
||||
|
||||
int main() {
|
||||
int n = 8; // 数组长度
|
||||
vector<int> B1(n + 1, 0), B2(n + 1, 0); // 初始化两个树状数组
|
||||
|
||||
// 对区间 [2, 5] 增加 3
|
||||
range_update(2, 5, 3, B1, B2, n);
|
||||
|
||||
// 查询区间 [1, 5] 的和
|
||||
cout << "Sum of range [1, 5]: " << range_query(1, 5, B1, B2) << endl; // 输出应为 12
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 总结
|
||||
|
||||
通过使用两个树状数组,我们可以有效地实现区间更新(即对一个区间内的所有元素增加或减少某个值)和区间查询操作。这种方法的核心思想是利用差分数组的概念来维护区间的影响,从而在树状数组的基础上扩展了其功能。
|
||||
在树状数组中使用“差分乘以位置”的技巧,主要是为了将区间更新转化为树状数组能够处理的前缀和查询。这个技巧通过两个树状数组的配合,确保在进行区间更新后,我们仍然可以高效地计算任意区间的和。
|
||||
|
||||
### 1. 问题背景
|
||||
|
||||
我们希望能够对数组 `arr` 的某个区间 `[l, r]` 内的所有元素同时加上一个值 `delta`,并且在之后能够快速地查询任意区间的和。如果直接对每个元素单独加上 `delta`,那么每次区间更新的复杂度将会是 \(O(n)\),这对于大数组来说非常不高效。因此,我们需要一种巧妙的方式来保持操作的效率。
|
||||
|
||||
### 2. 差分数组与位置的关系
|
||||
|
||||
回顾差分数组的原理:对于一个数组 `arr`,它的差分数组 `d` 是这样定义的:
|
||||
|
||||
\[ d[i] = arr[i] - arr[i-1] \]
|
||||
|
||||
如果我们对区间 `[l, r]` 的所有元素加上一个值 `delta`,差分数组会进行如下修改:
|
||||
|
||||
- `d[l] += delta`:表示从位置 `l` 开始,所有元素增加 `delta`。
|
||||
- `d[r+1] -= delta`:表示从位置 `r+1` 开始,恢复原来的数值(即对冲 `delta` 的影响)。
|
||||
|
||||
通过这个差分数组,我们可以将区间更新的影响从每个单独的元素转移到两个点上,从而使更新操作的复杂度降为 \(O(\log n)\)。
|
||||
|
||||
### 3. 为什么需要“差分乘以位置”
|
||||
|
||||
然而,仅仅使用一个差分数组并不足以处理查询区间和的问题。为了解决这个问题,我们使用两个树状数组:
|
||||
|
||||
- 一个用于存储基本的差分(`B1`)。
|
||||
- 另一个用于存储差分乘以位置的结果(`B2`)。
|
||||
|
||||
这里的“差分乘以位置”其实是为了在查询时恢复出正确的区间和。具体来说,使用差分乘以位置的原因如下:
|
||||
|
||||
#### 1. 维持累加效应的平衡
|
||||
假设我们在区间 `[l, r]` 增加 `delta`,我们希望每个位置 `i`(其中 `l ≤ i ≤ r`)都增加 `delta`。考虑查询前缀和时:
|
||||
|
||||
- 如果仅仅使用 `B1` 来存储差分数组,我们得到的是每个元素的差分累积量,这个累积量随着 `i` 增加。
|
||||
- 然而,如果我们想要恢复出真正的区间和,需要考虑这些差分对每个元素的累积影响。这时候,需要在 `B2` 中存储差分乘以位置的结果,这样在计算前缀和时就能抵消不必要的多加部分。
|
||||
|
||||
#### 2. 构建正确的前缀和公式
|
||||
假设我们希望查询区间 `[1, i]` 的和:
|
||||
|
||||
- 直接使用 `B1` 的查询结果(即 `query(i, B1) * i`)会累加过多的影响,因为 `query(i, B1)` 实际上只考虑到了基本差分,而没有考虑累积效应的差别。
|
||||
- 通过减去 `query(i, B2)`,我们就可以剔除多余的部分,得到正确的区间和。
|
||||
|
||||
因此,前缀和的计算公式为:
|
||||
|
||||
\[ \text{prefix\_sum}(i) = i \cdot \text{query}(i, B1) - \text{query}(i, B2) \]
|
||||
|
||||
这个公式通过引入 `B2` 来校正查询结果,确保我们得到的前缀和是准确的。
|
||||
|
||||
### 4. 具体作用
|
||||
|
||||
- `B1` 负责记录直接的差分变化。
|
||||
- `B2` 通过存储“差分乘以位置”的值,帮助校正前缀和中的多余累积,使得前缀和查询能够返回正确的值。
|
||||
|
||||
### 5. 总结
|
||||
|
||||
“差分乘以位置”的引入,是为了确保在树状数组处理区间更新时,我们能够正确计算任意前缀和或区间和。通过使用两个树状数组,一个记录基础差分,另一个记录差分乘以位置,我们可以在 \(O(\log n)\) 时间内实现区间更新和区间查询,保持了树状数组的高效性。在树状数组中使用“差分乘以位置”的技巧,主要是为了将区间更新转化为树状数组能够处理的前缀和查询。这个技巧通过两个树状数组的配合,确保在进行区间更新后,我们仍然可以高效地计算任意区间的和。
|
||||
|
||||
### 1. 问题背景
|
||||
|
||||
我们希望能够对数组 `arr` 的某个区间 `[l, r]` 内的所有元素同时加上一个值 `delta`,并且在之后能够快速地查询任意区间的和。如果直接对每个元素单独加上 `delta`,那么每次区间更新的复杂度将会是 \(O(n)\),这对于大数组来说非常不高效。因此,我们需要一种巧妙的方式来保持操作的效率。
|
||||
|
||||
### 2. 差分数组与位置的关系
|
||||
|
||||
回顾差分数组的原理:对于一个数组 `arr`,它的差分数组 `d` 是这样定义的:
|
||||
|
||||
\[ d[i] = arr[i] - arr[i-1] \]
|
||||
|
||||
如果我们对区间 `[l, r]` 的所有元素加上一个值 `delta`,差分数组会进行如下修改:
|
||||
|
||||
- `d[l] += delta`:表示从位置 `l` 开始,所有元素增加 `delta`。
|
||||
- `d[r+1] -= delta`:表示从位置 `r+1` 开始,恢复原来的数值(即对冲 `delta` 的影响)。
|
||||
|
||||
通过这个差分数组,我们可以将区间更新的影响从每个单独的元素转移到两个点上,从而使更新操作的复杂度降为 \(O(\log n)\)。
|
||||
|
||||
### 3. 为什么需要“差分乘以位置”
|
||||
|
||||
然而,仅仅使用一个差分数组并不足以处理查询区间和的问题。为了解决这个问题,我们使用两个树状数组:
|
||||
|
||||
- 一个用于存储基本的差分(`B1`)。
|
||||
- 另一个用于存储差分乘以位置的结果(`B2`)。
|
||||
|
||||
这里的“差分乘以位置”其实是为了在查询时恢复出正确的区间和。具体来说,使用差分乘以位置的原因如下:
|
||||
|
||||
#### 1. 维持累加效应的平衡
|
||||
假设我们在区间 `[l, r]` 增加 `delta`,我们希望每个位置 `i`(其中 `l ≤ i ≤ r`)都增加 `delta`。考虑查询前缀和时:
|
||||
|
||||
- 如果仅仅使用 `B1` 来存储差分数组,我们得到的是每个元素的差分累积量,这个累积量随着 `i` 增加。
|
||||
- 然而,如果我们想要恢复出真正的区间和,需要考虑这些差分对每个元素的累积影响。这时候,需要在 `B2` 中存储差分乘以位置的结果,这样在计算前缀和时就能抵消不必要的多加部分。
|
||||
|
||||
#### 2. 构建正确的前缀和公式
|
||||
假设我们希望查询区间 `[1, i]` 的和:
|
||||
|
||||
- 直接使用 `B1` 的查询结果(即 `query(i, B1) * i`)会累加过多的影响,因为 `query(i, B1)` 实际上只考虑到了基本差分,而没有考虑累积效应的差别。
|
||||
- 通过减去 `query(i, B2)`,我们就可以剔除多余的部分,得到正确的区间和。
|
||||
|
||||
因此,前缀和的计算公式为:
|
||||
|
||||
\[ \text{prefix\_sum}(i) = i \cdot \text{query}(i, B1) - \text{query}(i, B2) \]
|
||||
|
||||
这个公式通过引入 `B2` 来校正查询结果,确保我们得到的前缀和是准确的。
|
||||
|
||||
### 4. 具体作用
|
||||
|
||||
- `B1` 负责记录直接的差分变化。
|
||||
- `B2` 通过存储“差分乘以位置”的值,帮助校正前缀和中的多余累积,使得前缀和查询能够返回正确的值。
|
||||
|
||||
### 5. 总结
|
||||
|
||||
“差分乘以位置”的引入,是为了确保在树状数组处理区间更新时,我们能够正确计算任意前缀和或区间和。通过使用两个树状数组,一个记录基础差分,另一个记录差分乘以位置,我们可以在 \(O(\log n)\) 时间内实现区间更新和区间查询,保持了树状数组的高效性。
|
BIN
day11/FenwickTree/FenwickTree.pdf
Normal file
BIN
day11/FenwickTree/FenwickTree.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user