Update bit

This commit is contained in:
YDZ
2021-04-22 00:15:58 +08:00
parent ebd8951db4
commit 19e5c7e9c9

View File

@@ -304,17 +304,102 @@ func reversePair(s string) int {
}
```
上述代码中的 newPermutation 就是映射调整后的数组 B。遍历数组 B按顺序把元素插入到树状数组中。例如数组 B 是 int[9,8,5,4,6,2,3,8,7,1],现在往树状数组中插入 6代表 6 这个元素出现了。query() 查询 [1,6] 区间内是否有元素出现,区间前缀和代表区间内元素出现次数和。如果有 k 个元素出现,且当前插入了 5 个元素,那么 5-k 的差值即是逆序的元素个数,这些元素一定比 6 大。
上述代码中的 newPermutation 就是映射调整后的数组 B。遍历数组 B按顺序把元素插入到树状数组中。例如数组 B 是 int[9,8,5,4,6,2,3,8,7,1],现在往树状数组中插入 6代表 6 这个元素出现了。query() 查询 [1,6] 区间内是否有元素出现,区间前缀和代表区间内元素出现次数和。如果有 k 个元素出现,且当前插入了 5 个元素,那么 5-k 的差值即是逆序的元素个数,这些元素一定比 6 大。这种方法是正序构造树状数组。
还有一种方法是倒序构造树状数组。例如下面代码:
```go
for i := len(s) - 1; i > 0; i-- {
bit.Add(newPermutation[i], 1)
res += bit.Query(newPermutation[i] - 1)
}
```
由于是倒序插入,每次 Query 之前的元素下标一定比当前 i 要大。下标比 i 大,元素值比 A[i] 小,这样的元素和 i 可以构成逆序对。Query 查找 [1, B[j]] 区间内元素总个数,即为逆序对的总数。
> 注意,计算逆序对的时候不要算重复了。比如,计算当前 j 下标前面比 B[j] 值大的数,又算上 j 下标后面比 B[j] 值小的数。这样计算出现了很多重复。因为 j 下标前面的下标 k也会寻找 k 下标后面比 B[k] 值小的数,重复计算了。那么统一找比自己下标小,但是值大的元素,那么统一找比自己下标大,但是值小的元素。切勿交叉计算。
## 2. 求区间逆序对
给定 {{< katex >}} n {{< /katex >}} 个数的序列 {{< katex >}} A[n] \in [1,2^{31}-1] {{< /katex >}},然后给出 {{< katex >}} n \in [1,10^{5}] {{< /katex >}} 次询问 {{< katex >}} [L,R] {{< /katex >}},每次询问区间 {{< katex >}} [L,R] {{< /katex >}} 中满足 {{< katex >}} L \leqslant i < j \leqslant R {{< /katex >}} 且 {{< katex >}} A[i] > A[j] {{< /katex >}} 的下标 {{< katex >}} (i,j) {{< /katex >}} 的对数。
这个问题比上一题多了一个区间限制。这个区间的限制影响对逆序对的选择。例如:[1,3,5,2,1,1,8,9],求在 [2,5] 区间内的逆序数。元素 2 在区间内,比元素 2 的元素有 4 个。但是元素 3 在区间外,所以这个 3 不能参与逆序数的统计。
这个问题比上一题多了一个区间限制。这个区间的限制影响对逆序对的选择。例如:[1,3,5,2,1,1,8,9,8,6,5,3,7,7,2],求在 [3,7] 区间内的逆序数。元素 2 在区间内,比元素 2 的元素2 个。元素 3 和 5 在区间外,所以 3 和 5 不能参与逆序数的统计。比元素 2 小的元素也只有 2 个,黄色标识的 3 个 1 都比 2 小,但是第一个 1 不能算在内,因为它在区间外。
先将所有查询区间按照右端点单调不减排序,如下图所示。
> 这里也可以按照查询区间左端点单调不增排序。如果这样排序,下面构建树状数组需要倒序插入。并且查找的是下标靠后但是元素值小的逆序对。两者方法都可以实现,这里讲解选其中一种。
![](https://img.halfrost.com/Blog/ArticleImage/152_1_0.png)
总的区间覆盖的范围决定了树状数组待插入数字的范围。如上图,总的区间位于 [1,12],那么下标为 01314 的元素不需要理会,它们不会被用到,所以也不用插入到树状数组中。
求区间逆序对的过程中还需要利用到一个辅助数组 C[k],这个数组的含义是下标为 k 的元素,在插入到树状数组之前,比 A[k] 值小的元素有几个。举个例子,例如下标为 7 的元素值为 9 。C[7] = 6因为当前比 9 小的元素是 352118。这个辅助数组 C[k] 的意义是找到下标比它小,且元素值也比它小的元素个数。
由于这里选择区间右区间排序,所以构造树状数组插入是顺序插入。这样区间从左有右的查询可以依次得到结果。如上图中最下一行的图示,假设当前查询到了第 4 个区间。第 4 个区间包含元素值 1,8,9,8,6,5 。当前从左往右插入构造树状数组,已经插入了下标为 [1,10] 区间的元素值即如图显示插入的数值。现在遍历查询区间内所有元素Query(A[i] - 1) - C[i] 即为下标 i 在当前查询区间内的逆序对总个数。例如元素 9
{{< katex display >}}
\begin{aligned}
Query(A[i] - 1) - C[i] &= Query(A[7] - 1) - C[7] \\
&= Query(9 - 1) - C[7] = Query(8) - C[7]\\
&= 9 - 6 = 3\\
\end{aligned}
{{< /katex >}}
插入 A[i] 元素构造树状数组在先Query() 查询针对当前全局情况,即查询下标 [1,10] 区间内所有比元素 9 小的元素总数,不难发现所有元素都比元素 9 小,那么 Query(A[i] - 1) 得到的结果是 9。C[7] 是元素 9 插入到树状数组之前比元素 9 小的元素总数,是 6。两者相减最终结果是 9 - 6 = 3。看上图也很容易看出来结果是正确的在区间内比 9 下标值大且元素值比 9 小的只有 3 个,分别对应的下标是 8910对应的元素值是 865。
总结:
1. 离散化数组 A[i]
2. 对所有区间按照右端点单调不减排序
3. 按照区间排序后的结果,从左往右依次遍历每个区间。依照从左往右的区间覆盖元素范围,从左往右将 A[i] 插入至树状数组中,每个元素插入之前计算辅助数组 C[i]。
4. 依次遍历每个区间内的所有元素,对每个元素计算 Query(A[i] - 1) - C[i],累加逆序对的结果即是这个区间所有逆序对的总数。
## 3. 求树上逆序对
给定 {{< katex >}} n \in [0,10^{5}] {{< /katex >}} 个结点的树,求每个结点的子树中结点编号比它小的数的个数。
## 四. 二维树状数组
树上逆序对的问题可以通过树的先序遍历可以将树转换成数组,令树上的某个结点 i先序遍历到的顺序为 pre[i]i 的子结点个数为 a[i],则转换成数组后 i 管理的区间为 [pre[i], pre[i] + a[i] - 1],然后就可以转换成区间逆序对问题进行求解了。
## 四. 二维树状数组
树状数组可以扩展到二维、三维或者更高维。二维树状数组可以解决离散平面上的统计问题。
```go
// BinaryIndexedTree2D define
type BinaryIndexedTree2D struct {
tree [][]int
row int
col int
}
// Add define
func (bit2 *BinaryIndexedTree2D) Add(i, j int, val int) {
for i <= bit2.row {
k := j
for k <= bit2.col {
bit2.tree[i][k] += val
k += lowbit(k)
}
i += lowbit(i)
}
}
// Query define
func (bit2 *BinaryIndexedTree2D) Query(i, j int) int {
sum := 0
for i >= 1 {
k := j
for k >= 1 {
sum += bit2.tree[i][k]
k -= lowbit(k)
}
i -= lowbit(i)
}
return sum
}
```