当前位置:   article > 正文

通俗易懂讲区间问题:前缀和数组、差分数组、线段树、树状数组(完结)_区间查询前缀和树状数组差分数组线段树

区间查询前缀和树状数组差分数组线段树

前缀和数组、差分数组、线段树、树状数组均用于处理区间问题,但有不同应用场景

  1. 前缀和数组:用于处理 连续多次取区间和 操作的情况,取区间和期间不能对原数组进行区间增减数操作

  2. 差分数组:用于处理 多次区间增减数操作,最后读取一次区间和(即修改期间读取不频繁) 操作的情况

  3. 线段树:用于处理 多次区间增减数操作,期间多次读取区间和(即修改期间读取频繁) 操作的情况

  4. 树状数组:用于处理 多次区间增减数操作,期间多次读取区间和(即修改期间读取频繁) 操作的情况,同线段树的情况、但树状数组更加简单且不支持区间更新,线段树支持区间更新但编码复杂

一、前缀和数组(一维)

在学习之前,千万不要忘记何时使用前缀和数组

前缀和数组:用于处理 连续多次取区间和 操作的情况,取区间和期间不能对原数组进行区间增减数操作

具体思想:

1. 求前缀和数组

假定原数组arr,前缀和数组prefixArr,当前讨论位置为i

那么有prefixArr[i]=arr[i]+arr[i-1]+arr[i-2]+......+arr[0](即i位置及之前所有数累加和==>前缀 和)

由于prefixArr[i-1]=arr[i-1]+arr[i-2]+......+arr[0]

因此化简原式:prefixArr[i]=prefixArr[i-1]+arr[i]

2. 求区间和

设求[i,j](j>i)区间的区间和

即求arr[i]+arr[i+1]+......+arr[j-1]+arr[j]

由于prefixArr[j]=arr[j]+arr[j-1]+arr[j-2]+......arr[0],prefixArr[i-1]=arr[i-1]+arr[i-2]+arr[i-3]+......+arr[0]

因此所求区间和为prefixArr[j]-prefixArr[i-1]

简单来说如下:

红色区域为原数组,黄色区域为所求部分(区间和),那么我们先求蓝色区域与绿色区域(前缀和),再相减就得到了黄色区域

3. 代码如下

  1. public class PrefixArr {
  2. public static void main(String[] args) {
  3. //获取数组
  4. int[] arr = getArr(10);
  5. for (int i = 0; i < arr.length; i++) {
  6. System.out.print(arr[i] + " ");
  7. }
  8. System.out.println();
  9. //前缀数组
  10. int[] prefixArr = new int[arr.length];
  11. prefixArr[0] = arr[0];
  12. for (int i = 1; i < arr.length; i++) {
  13. prefixArr[i] = prefixArr[i - 1] + arr[i];
  14. }
  15. //获取[i,j]区间的数
  16. int i = 3;
  17. int j = 5;
  18. int ijNum = prefixArr[j] - prefixArr[i - 1];
  19. System.out.println("ijNum: " + ijNum);
  20. }
  21. private static int[] getArr(int len) {
  22. int[] arr = new int[len];
  23. for (int i = 0; i < arr.length; i++) {
  24. arr[i] = (int) (Math.random() * 9 + 1);
  25. }
  26. return arr;
  27. }
  28. }

二、差分数组(一维)

在学习之前,千万不要忘记何时使用差分数组

差分数组:用于处理 多次区间增减数操作,最后读取一次区间和(即修改期间读取不频繁) 操作的情况

具体思想:

1. 初始化差分数组

假定原数组arr,差分数组diff,注意差分数组diff的长度要比原数组长度多1(或者在区间修改时进行判断,这点在后面会提到);差分数组所有位置初始为0

2. 接下来进行区间修改

因为差分数组是用于多次区间加减数操作后读取一次的情况,这里进行模拟区间增减数

如果要求在区间[i,j]每个数上增加数value,那么规定修改差分数组diff[i]+value,diff[j+1]-value。从这里可以知道为什么diff数组的长度需要比原数组多1,或者在区间修改的时候进行判断,防止数组越界

3. 通过差分数组获取更新值后的 更新数组

定理:差分数组的前缀和是更新数组,更新数组的含义是在[i,j]区间加value,那么更新数组下标从i到j都为value

证明:

设已有差分数组diff,更新数组update,在区间[i,j]每个数上添加value值,其余位置不更新

根据规定有:diff[i]=diff[i]+value=0+value=value,diff[j+1]=diff[j+1]-value=0-value=-value

那么现在求diff的前缀和数组prefix

prefix[0]=diff[0]=0

prefix[1]=diff[0]+diff[1]=prefix[0]+diff[1]=0

prefix[i]=diff[0]+diff[1]+......+diff[i-1]+diff[i]=value

prefix[i+1]=diff[0]+diff[1]+diff[2]+......+diff[i]+diff[i+1]=prefix[i]+diff[i+1]=value+0=value

......

prefix[j]=prefix[j-1]+diff[j]=value+0=value

prefix[j+1]=prefix[j]+diff[j+1]=value+(-value)=0

所以差分数组前缀和数组就是更新数组update

i位置加value会影响 更新数组 i位置以后的位置加value(由于更新数组是差分数组的前缀和),即蓝色和黄色的区域都加value

j+1位置减value会影响 更新数组 j+1位置以后的位置减value(由于更新数组是差分数组的前缀和),即黄色区域减value

所以在经过修改差分数组diff[i]+value,diff[j+1]-value后再求差分数组前缀和数组就是更新数组

4. 代码如下

  1. public class DiffArr {
  2. public static void main(String[] args) {
  3. //获取数组
  4. int[] arr = getArr(10);
  5. for (int i = 0; i < arr.length; i++) {
  6. System.out.print(arr[i] + " ");
  7. }
  8. System.out.println();
  9. //差分数组
  10. int[] diff = new int[arr.length + 1];
  11. //区间修改,如在区间[i,j]加v <==>d[L]+v,d[R+1]-v 因此差分数组要多一个数
  12. addRange(2, 4, diff, 10);
  13. addRange(1, 9, diff, -5);
  14. //差分数组的前缀和是更新区域的更新值
  15. for (int i = 1; i < diff.length; i++) {
  16. diff[i] = diff[i] + diff[i - 1];
  17. }
  18. //更新后的数组是原数组+更新数组
  19. for (int i = 0; i < arr.length; i++) {
  20. System.out.print((arr[i]+diff[i]) + " ");
  21. }
  22. }
  23. public static void addRange(int i, int j, int[] diff, int value) {
  24. diff[i] += value;
  25. diff[j + 1] -= value;
  26. }
  27. private static int[] getArr(int len) {
  28. int[] arr = new int[len];
  29. for (int i = 0; i < arr.length; i++) {
  30. arr[i] = (int) (Math.random() * 9 + 1);
  31. }
  32. return arr;
  33. }
  34. }

三、前缀和数组(二维)

具体思想:

1. 求二维前缀和

定义:二维前缀和是指(0,0)位置与(i,j)围成矩阵内数的和

假定原二维数组为arr,二维前缀和数组prefixArr,求(i,j)位置的前缀和

prefixArr[i][j]=(arr[0][0]+arr[0][1]+arr[0][2]+......+arr[0][j])+(arr[1][0]+arr[1][1]+arr[1][2]+......+arr[1][j])+......+(arr[i][0]+arr[i][1]+arr[i][2]+......+arr[i][j])

由于

prefixArr[i-1][j] = (arr[0][0]+arr[0][1]+arr[0][2]+......+arr[0][j])+(arr[1][0]+arr[1][1]+arr[1][2]+......+arr[1][j])+......+(arr[i-1][0]+arr[i-1][1]+arr[i-1][2]+......+arr[i-1][j])

prefixArr[i][j-1] = (arr[0][0]+arr[0][1]+arr[0][2]+......+arr[0][j-1])+(arr[1][0]+arr[1][1]+arr[1][2]+......+arr[1][j-1])+......+(arr[i][0]+arr[i][1]+arr[i][2]+......+arr[i][j-1])

prefixArr[i-1][j-1] = (arr[0][0]+arr[0][1]+arr[0][2]+......+arr[0][j-1])+(arr[1][0]+arr[1][1]+arr[1][2]+......+arr[1][j-1])+......+(arr[i-1][0]+arr[i-1][1]+arr[i-1][2]+......+arr[i-1][j-1])

因此化简:prefixArr[i][j]=prefixArr[i-1][j]+prefixArr[i][j-1] -prefixArr[i-1][j-1]+arr[i][j]

prefixArr[i-1][j]=蓝色+黄色

prefixArr[i][j-1]=绿色+黄色

prefixArr[i-1][j-1]=黄色

prefixArr[i][j]=红色+蓝色+黄色+绿色=(红色)+(蓝色+黄色)+(绿色+黄色)-黄色=arr[i][j]+prefixArr[i-1][j]+prefix[i][j-1]-prefix[i-1][j-1]

2. 求区间和 (i,j)->(m,n)的区间和

根据图很容易看出:prefixArr[m][n] - prefixArr[i - 1][n] - prefixArr[m][j - 1] + prefixArr[i - 1][j - 1];

3. 代码如下

  1. public class PrefixArr2Dim {
  2. public static void main(String[] args) {
  3. int[][] arr = getArr2Dim(8, 8);
  4. for (int i = 0; i < arr.length; i++) {
  5. for (int j = 0; j < arr[i].length; j++) {
  6. System.out.print(arr[i][j] + " ");
  7. }
  8. System.out.println();
  9. }
  10. System.out.println("==========================");
  11. //求前缀和
  12. int[][] prefixArr = new int[arr.length][arr[0].length];
  13. prefixArr[0][0] = arr[0][0];
  14. //第一行与第一列需要特殊处理
  15. for (int j = 1; j < arr[0].length; j++) {
  16. prefixArr[0][j] = prefixArr[0][j - 1] + arr[0][j];
  17. }
  18. for (int i = 1; i < arr.length; i++) {
  19. prefixArr[i][0] = prefixArr[i - 1][0] + arr[i][0];
  20. }
  21. for (int i = 1; i < arr.length; i++) {
  22. for (int j = 1; j < arr[i].length; j++) {
  23. prefixArr[i][j] = prefixArr[i - 1][j] + prefixArr[i][j - 1] - prefixArr[i - 1][j - 1] + arr[i][j];
  24. }
  25. }
  26. for (int i = 0; i < prefixArr.length; i++) {
  27. for (int j = 0; j < prefixArr[i].length; j++) {
  28. System.out.print(prefixArr[i][j] + " ");
  29. }
  30. System.out.println();
  31. }
  32. System.out.println("==========================");
  33. //求区间和 [i,j]与[m,n]的区间和
  34. int i = 2, j = 3, m = 7, n = 6;
  35. int sum = prefixArr[m][n] - prefixArr[i - 1][n] - prefixArr[m][j - 1] + prefixArr[i - 1][j - 1];
  36. System.out.println(sum);
  37. }
  38. public static int[][] getArr2Dim(int firstDim, int secondDim) {
  39. int[][] arr = new int[firstDim][secondDim];
  40. for (int i = 0; i < arr.length; i++) {
  41. for (int j = 0; j < arr[i].length; j++) {
  42. arr[i][j] = (int) (Math.random() * 9 + 1);
  43. }
  44. }
  45. return arr;
  46. }
  47. }

四、差分数组(二维)

1. 首先要理解:以下为diff差分数组

如果在(i,j)位置加value值,那么会影响第四象限的所有位置的值加value,如图:

所以如果现在要在(i,j)->(m,n)矩阵区域加value,如图蓝色区域

(i,j)添加value,会影响 蓝色+橙色+绿色+红色 区域

(i,n+1)添加value,会影响 橙色+红色 区域

(m+1,j)添加value,会影响 绿色+红色 区域

(m+1,n+1)添加value,会影响 红色 区域

所以要只让蓝色区域加value,要在(i,j)位置加value,(i,n+1)位置减value,(m+1,j)位置减value,(m+1,n+1)位置加value

2. 差分数组求前缀和是更新数组

3. 更新数组加上原数组是更新修改后的数组

4.代码

  1. public class DiffArr2Dim {
  2. public static void main(String[] args) {
  3. //获取原数组
  4. int[][] arr = getArr2Dim(7, 7);
  5. //输出
  6. for (int i = 0; i < arr.length; i++) {
  7. for (int j = 0; j < arr[i].length; j++) {
  8. System.out.print(arr[i][j]+" ");
  9. }
  10. System.out.println();
  11. }
  12. System.out.println("============================");
  13. //求差分数组
  14. int[][] diff = new int[arr.length + 1][arr[0].length + 1];
  15. addRegion(diff, 0, 0, 2, 3, 10);
  16. addRegion(diff, 1, 1, 5, 6, -10);
  17. //求差分数组前缀和
  18. int[][] prefixArr = new int[arr.length][arr[0].length];
  19. prefixArr[0][0] = diff[0][0];
  20. //第一行与第一列特殊处理
  21. for (int j = 1; j < arr[0].length; j++) {
  22. prefixArr[0][j] = prefixArr[0][j - 1] + diff[0][j];
  23. }
  24. for (int i = 1; i < arr.length; i++) {
  25. prefixArr[i][0] = prefixArr[i - 1][0] + diff[i][0];
  26. }
  27. //其余位置
  28. for (int i = 1; i < arr.length; i++) {
  29. for (int j = 1; j < arr[i].length; j++) {
  30. prefixArr[i][j] = prefixArr[i - 1][j] + prefixArr[i][j - 1] - prefixArr[i - 1][j - 1] + diff[i][j];
  31. }
  32. }
  33. //输出
  34. for (int i = 0; i < prefixArr.length; i++) {
  35. for (int j = 0; j < prefixArr[i].length; j++) {
  36. System.out.print(prefixArr[i][j]+" ");
  37. }
  38. System.out.println();
  39. }
  40. System.out.println("============================");
  41. //输出修改后的数组
  42. for (int i = 0; i < arr.length; i++) {
  43. for (int j = 0; j < arr[i].length; j++) {
  44. System.out.print((arr[i][j]+diff[i][j])+" ");
  45. }
  46. System.out.println();
  47. }
  48. }
  49. private static void addRegion(int[][] diff, int i, int j, int m, int n, int value) {
  50. diff[i][j] += value;
  51. diff[i][n + 1] -= value;
  52. diff[m + 1][j] -= value;
  53. diff[m + 1][n + 1] += value;
  54. }
  55. public static int[][] getArr2Dim(int firstDim, int secondDim) {
  56. int[][] arr = new int[firstDim][secondDim];
  57. for (int i = 0; i < arr.length; i++) {
  58. for (int j = 0; j < arr[i].length; j++) {
  59. arr[i][j] = (int) (Math.random() * 9 + 1);
  60. }
  61. }
  62. return arr;
  63. }
  64. }

五、线段树

在学习之前,千万不要忘记何时使用线段树

线段树:用于处理 多次区间增减数操作,期间多次读取区间和(即修改期间读取频繁) 操作的情况

具体思想:

  1. 将区间的增减更新任务揽住,不下发

  2. 当有新的任务到来,下发一层

  3. 由于使用树状区间结构揽住任务,每次任务到来时只需要log(N)的时间复杂度

  4. 叶子结点代表原数组arr中的某一索引位置的值,内部节点代表原数组arr中某一范围的区间和


  1. public static class SegmentTree {
  2. private int MAXN;//最大长度
  3. private int[] arr;//存储原数组,但下标从1开始
  4. private int[] sum;//存储区间和
  5. private int[] lazy;//存储揽住增加的值
  6. private int[] change;//存储揽住更新的值
  7. private boolean[] update;//标记change是否是更新
  8. public SegmentTree(int[] origin) {
  9. MAXN = origin.length + 1;
  10. arr = new int[MAXN];
  11. for (int i = 1; i < MAXN; i++) {
  12. arr[i] = origin[i - 1];
  13. sum = new int[MAXN << 2];
  14. lazy = new int[MAXN << 2];
  15. change = new int[MAXN << 2];
  16. update = new boolean[MAXN << 2];
  17. }
  18. }
  19. //rt位置的sum(也就是rt节点)=rt左孩子的sum+rt右孩子的sum
  20. private void pushUp(int rt) {
  21. sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
  22. }
  23. // 初始化阶段:把sum数组,填好
  24. // 代表当前sum的rt位置=arr的l-r范围的和
  25. public void build(int l, int r, int rt) {
  26. if (l == r) {
  27. sum[rt] = arr[l];
  28. return;
  29. }
  30. int mid = (l + r) >> 1;
  31. //构造左
  32. build(l, mid, rt << 1);
  33. //构造右
  34. build(mid + 1, r, rt << 1 | 1);
  35. //求和
  36. pushUp(rt);
  37. }
  38. //在L-R范围上加C
  39. //l-r,当前rt位置的格子代表的范围
  40. public void add(
  41. int L, int R, int C,
  42. int l, int r,
  43. int rt
  44. ) {
  45. if (L <= l && R >= r) {
  46. sum[rt] += C * (r - l + 1);
  47. lazy[rt] += C;
  48. return;
  49. }
  50. //任务没有全包
  51. int mid = (l + r) >> 1;
  52. pushDown(rt, mid - l + 1, r - mid);
  53. //需要发给左边再发
  54. if (L <= mid) {
  55. add(L, R, C, l, mid, rt << 1);
  56. }
  57. //需要发给右边再发
  58. if (R > mid) {
  59. add(L, R, C, mid + 1, r, rt << 1 | 1);
  60. }
  61. //左右都更新好,现在更新当前
  62. pushUp(rt);
  63. }
  64. public void update(
  65. int L, int R, int C,
  66. int l, int r, int rt
  67. ) {
  68. if (L <= l && R >= r) {
  69. update[rt] = true;
  70. change[rt] = C;
  71. sum[rt] = C * (r - l + 1);
  72. lazy[rt] = 0;
  73. return;
  74. }
  75. //揽不住
  76. int mid = (l + r) >> 1;
  77. pushDown(rt, mid - l + 1, r - mid);
  78. //需要发给左边再发
  79. if (L <= mid) {
  80. update(L, R, C, l, mid, rt << 1);
  81. }
  82. //需要发给右边再发
  83. if (R > mid) {
  84. update(L, R, C, mid + 1, r, rt << 1 | 1);
  85. }
  86. //左右都更新好,现在更新当前
  87. pushUp(rt);
  88. }
  89. //ln->左孩子范围的数量,rn->右孩子范围的数量
  90. private void pushDown(int rt, int ln, int rn) {
  91. if (update[rt]) {
  92. update[rt << 1] = true;
  93. update[rt << 1 | 1] = true;
  94. change[rt << 1] = change[rt];
  95. change[rt << 1 | 1] = change[rt];
  96. lazy[rt << 1] = 0;
  97. lazy[rt << 1 | 1] = 0;
  98. sum[rt << 1] = change[rt] * ln;
  99. sum[rt << 1 | 1] = change[rt] * rn;
  100. update[rt] = false;
  101. }
  102. if (lazy[rt] != 0) {
  103. lazy[rt << 1] += lazy[rt];
  104. sum[rt << 1] += lazy[rt] * ln;
  105. lazy[rt << 1 | 1] += lazy[rt];
  106. sum[rt << 1 | 1] += lazy[rt] * rn;
  107. lazy[rt] = 0;
  108. }
  109. }
  110. public long query(int L, int R, int l, int r, int rt) {
  111. if (L <= l && R >= r) {
  112. return sum[rt];
  113. }
  114. int mid = (l + r) >> 1;
  115. pushDown(rt, mid - l + 1, r - mid);
  116. long ans = 0;
  117. if (L <= mid) {
  118. ans += query(L, R, l, mid, rt << 1);
  119. }
  120. if (R > mid) {
  121. ans += query(L, R, mid + 1, r, rt << 1 | 1);
  122. }
  123. return ans;
  124. }
  125. }
  1. #include <iostream>
  2. using namespace std;
  3. class SegmentTree
  4. {
  5. public:
  6. int MAXN;
  7. int *arr;
  8. int *sum;
  9. int *lazy;
  10. int *change;
  11. bool *update;
  12. SegmentTree(int *origin, int n)
  13. {
  14. int MAXN = n + 1;
  15. this->arr = new int[MAXN];
  16. for (int i = 1; i < MAXN; i++)
  17. {
  18. this->arr[i] = origin[i - 1];
  19. }
  20. this->sum = new int[MAXN];
  21. this->lazy = new int[MAXN];
  22. this->change = new int[MAXN];
  23. this->update = new bool[MAXN];
  24. for (int i = 0; i < MAXN; i++)
  25. {
  26. this->sum[i] = 0;
  27. this->lazy[i] = 0;
  28. this->change[i] = 0;
  29. this->update[i] = false;
  30. }
  31. }
  32. // 初始化
  33. void build(int l, int r, int rt)
  34. {
  35. if (l >= r)
  36. {
  37. this->sum[rt] = this->arr[l];
  38. return;
  39. }
  40. int mid = (l + r) >> 1;
  41. build(l, mid, rt << 1);
  42. build(mid + 1, r, rt << 1 | 1);
  43. pushUp(rt);
  44. }
  45. // add
  46. void add_f(
  47. int L, int R, int C,
  48. int l, int r,
  49. int rt)
  50. {
  51. if (L <= l && R >= r)
  52. {
  53. this->sum[rt] += (r - l + 1) * C;
  54. this->lazy[rt] += C;
  55. return;
  56. }
  57. int mid = (l + r) >> 1;
  58. pushDown(rt, mid - l + 1, r - mid); // 下发任务
  59. if (L <= mid)
  60. {
  61. add_f(L, R, C, l, mid, rt << 1);
  62. }
  63. if (R > mid)
  64. {
  65. add_f(L, R, C, mid + 1, r, rt << 1 | 1);
  66. }
  67. pushUp(rt);
  68. }
  69. // update
  70. void update_f(
  71. int L, int R, int C,
  72. int l, int r,
  73. int rt)
  74. {
  75. if (L <= l && R >= r)
  76. {
  77. this->update[rt] = true;
  78. this->change[rt] = C;
  79. this->sum[rt] = C * (r - l + 1);
  80. this->change[rt] = 0;
  81. return;
  82. }
  83. int mid = (l + r) >> 1;
  84. pushDown(rt, mid - l + 1, r - mid);
  85. if (L <= mid)
  86. {
  87. update_f(L, R, C, l, mid, rt << 1);
  88. }
  89. if (R > mid)
  90. {
  91. update_f(L, R, C, mid + 1, r, rt << 1 | 1);
  92. }
  93. }
  94. // query
  95. long query(
  96. int L, int R, int C,
  97. int l, int r,
  98. int rt)
  99. {
  100. if (L <= l && R >= r)
  101. {
  102. return this->sum[rt];
  103. }
  104. int mid = (l + r) >> 1;
  105. pushDown(rt, mid - l + 1, r - mid);
  106. long ans = 0;
  107. if (L <= mid)
  108. {
  109. ans += query(L, R, C, l, mid, rt << 1);
  110. }
  111. if (R > mid)
  112. {
  113. ans += query(L, R, C, mid + 1, r, rt << 1 | 1);
  114. }
  115. // pushUp(rt);
  116. return ans;
  117. }
  118. void pushDown(int rt, int ln, int rn)
  119. {
  120. if (this->update[rt])
  121. {
  122. this->update[rt << 1] = true;
  123. this->update[rt << 1 | 1] = true;
  124. this->change[rt << 1] = this->change[rt];
  125. this->change[rt << 1 | 1] = this->change[rt];
  126. this->sum[rt << 1] = ln * this->change[rt];
  127. this->sum[rt << 1 | 1] = rn * this->change[rt];
  128. this->lazy[rt << 1] = 0;
  129. this->lazy[rt << 1 | 1] = 0;
  130. this->update[rt] = false;
  131. }
  132. if (this->lazy[rt] != 0)
  133. {
  134. this->lazy[rt << 1] += this->lazy[rt];
  135. this->sum[rt << 1] += this->lazy[rt] * ln;
  136. this->lazy[rt << 1 | 1] += this->lazy[rt];
  137. this->sum[rt << 1 | 1] += this->lazy[rt] * rn;
  138. }
  139. }
  140. void pushUp(int rt)
  141. {
  142. this->sum[rt] = this->sum[rt << 1] + this->sum[rt << 1 | 1];
  143. }
  144. };
  145. int main()
  146. {
  147. return 0;
  148. }

六、树状数组

在学习之前千万不要忘记何时使用树状数组

用于处理 单点增减操作,期间多次查询 操作的情况,不如线段树强,但非常容易改造为一维、二维、三维的结构

具体思想:

1. tree数组i位置管理origin数组的部分和

如:

tree[1] = origin[1]

tree[2] = orgin[1]+origin[2]

tree[3] = origin[3]

tree[4] = origin[1]+origin[2]+orgin[3]+origin[4]

tree[5] = origin[5]

...

tree[8] = origin[1]+...+orgin[8]

....

具体算法:

tree[index]管理origin[index二进制表示减最后一个1再加1--index二进制]

如:

Index = 6 => 0110,因此管理origin[0101]到origin[0110]位置的数和

2. 现求origin的index位置到1位置的数和(也就是index位置的前缀和)

(1)index求出二进制xxxxxxx

(2)每次取tree[xxxxxxx]

(3)xxxxxxx减去最后一个1,循环2、3步骤

证明:现求7位置的前缀和

7= 0111,tree的0111位置管理origin的0111-0111的数=>tree[0111]

减去最后一个1=>0110,tree的0110位置管理origin的0101-0110的数=>tree[0110]

减去最后一个1=>0100,tree的0100位置管理origin的0001-0100的数=>tree[0100]

tree[0111]+tree[0110]+tree[0100]=>前缀和,得证

3. 若现在对origin的index位置加d,那么会影响tree的哪些位置

(1)index求出二进制xxxxxxx

(2)tree[xxxxxxx]+d

(3)xxxxxxx加上最后一个1,循环2、3步骤

一维:

  1. public static class IndexTree {
  2. private int N;
  3. private int[] tree;
  4. public IndexTree(int size,int[] origin) {
  5. this.N = size;
  6. this.tree = new int[N + 1];//树状数组下标从1开始
  7. }
  8. //每次去掉最后一个一
  9. public int sum(int index) {
  10. int ret = 0;
  11. while (index > 0) {
  12. ret += tree[index];
  13. index -= (index) & (-index);
  14. }
  15. return ret;
  16. }
  17. //每次加上最后一个一
  18. public void add(int index, int d) {
  19. while (index <= N) {
  20. this.tree[index] += d;
  21. index += index & (-index);
  22. }
  23. }
  24. }
  1. class IndexTree
  2. {
  3. public:
  4. int N;
  5. int *tree;
  6. IndexTree(int size, int *origin)
  7. {
  8. this->N = size;
  9. this->tree = new int[N + 1];
  10. }
  11. // sum
  12. int sum(int index)
  13. {
  14. int ans = 0;
  15. while (index > 0)
  16. {
  17. ans += tree[index];
  18. index -= (index) & (-index);
  19. }
  20. return ans;
  21. }
  22. void add(int index, int d)
  23. {
  24. while (index <= N)
  25. {
  26. tree[index] += d;
  27. index += index & (-index);
  28. }
  29. }
  30. };

二维:

  1. public static class IndexTree2D {
  2. int N;
  3. int M;
  4. int[][] tree;
  5. int[][] nums;
  6. IndexTree2D(int[][] matrix) {
  7. N = matrix.length;
  8. M = matrix[0].length;
  9. tree = new int[N + 1][M + 1];
  10. nums = new int[N][M];
  11. for (int i = 0; i < N; i++) {
  12. for (int j = 0; j < M; j++) {
  13. update(i, j, matrix[i][j]);
  14. }
  15. }
  16. }
  17. private int sum(int row, int col) {
  18. int sum = 0;
  19. for (int i = row + 1; i > 0; i -= i & -i) {
  20. for (int j = col + 1; j > 0; j -= j & -j) {
  21. sum += tree[i][j];
  22. }
  23. }
  24. return sum;
  25. }
  26. public void update(int row, int col, int val) {
  27. if (N == 0 || M == 0) {
  28. return;
  29. }
  30. int add = val - nums[row][col];
  31. nums[row][col] = val;
  32. for (int i = row + 1; i <= N; i += i & -i) {
  33. for (int j = col + 1; j <= M; j += j & -j) {
  34. tree[i][j] += add;
  35. }
  36. }
  37. }
  38. //从(row1,col1)->(row2,col2)
  39. int queryRange(int row1, int col1, int row2, int col2) {
  40. return sum(row2, col2) - sum(row1 - 1, col2) - sum(row2, col1 - 1) + sum(row1 - 1, col1 - 1);
  41. }
  42. }

  1. class IndexTree2D
  2. {
  3. public:
  4. int N;
  5. int M;
  6. int **tree;
  7. int **nums;
  8. IndexTree2D(int **matrix, int N, int M)
  9. {
  10. this->N = N;
  11. this->M = M;
  12. this->tree = new int *[N + 1];
  13. this->nums = new int *[N];
  14. for (int i = 0; i < N; i++)
  15. {
  16. this->tree[i + 1] = new int[M + 1];
  17. this->nums[i] = new int[M];
  18. }
  19. for (int i = 0; i < N; i++)
  20. {
  21. for (int j = 0; j < M; j++)
  22. {
  23. this->tree[i + 1][j + 1] = 0;
  24. this->nums[i][j] = 0;
  25. }
  26. }
  27. for (int i = 0; i < N; i++)
  28. {
  29. for (int j = 0; j < M; j++)
  30. {
  31. update(i, j, matrix[i][j]);
  32. }
  33. }
  34. }
  35. void update(int row, int col, int val)
  36. {
  37. if (N == 0 || M == 0)
  38. return;
  39. int add = val - nums[row][col];
  40. nums[row][col] = val;
  41. for (int i = row + 1; i <= N; i += i & -i)
  42. {
  43. for (int j = col + 1; j <= M; j += j & -j)
  44. {
  45. tree[i][j] += add;
  46. }
  47. }
  48. }
  49. int sum(int row, int col)
  50. {
  51. int ans = 0;
  52. for (int i = row + 1; i > 0; i -= i & -i)
  53. {
  54. for (int j = col + 1; j > 0; j -= j & -j)
  55. {
  56. ans += tree[i][j];
  57. }
  58. }
  59. return ans;
  60. }
  61. };

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/654283
推荐阅读
相关标签
  

闽ICP备14008679号