一:前缀和

题目:http://liuseroj.picp.io/problem/3205
思想:
求区间和的时候本质是:s[r] - s[l - 1]。
当我求1- 10区间的数的时候,本质 s[10] - s[0],这里就是为什么从1存数据的原因,属于处理边界的手段。处理任何区间都按照相同的运算方式,不用特殊处理。

注意适用于数组下表比较小的范围,注意和离散化的第一个例题做对比,数据范围10^9就会非常大。

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int n, m; // m代表计算区间和的次数
  5. int a[N], s[N];
  6. int main() {
  7. scanf("%d%d", &n, &m);
  8. for (int i = 1; i <= n; i++) scanf("%d", &a[i]); // cpp数组元素没有赋予初始值的时候,默认是0
  9. for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];// 利用空间换时间的原理,提前计算前i个数的和
  10. while (m--) {
  11. int l, r;
  12. scanf("%d%d", &l, &r); // 输入区间
  13. cout << s[r] - s[l - 1] << endl; // 计算某个区间实际用前缀和相减的方式就能实现。
  14. }
  15. return 0;
  16. }
  • 数据规模大于等于100万的话,建议使用scanf,否则建议使用cin

二:矩阵的和(二维求矩阵和)

题目网址:http://liuseroj.picp.io/problem/3206
原理和一维的本质相类似,其实本质就是公式

求s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
求二维数组面积差额s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 1010;
  4. int n, m, q;
  5. int a[N][N], s[N][N];
  6. int main() {
  7. scanf("%d%d%d", &n, &m, &q);
  8. for (int i = 1; i <= n; i++)
  9. for (int j = 1; j <= m; j++)
  10. scanf("%d", &a[i][j]);
  11. for (int i = 1; i <= n; i++ )
  12. for (int j = 1; j <= m; j++)
  13. s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
  14. while (q--) {
  15. int x1, x2, y1, y2;
  16. scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
  17. cout << s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1] << endl;
  18. }
  19. return 0;
  20. }

三:差分

前缀和差分本质:
a[N],b[N]

a[N]是b[N]的前缀和数组,b[N]叫做a[N]的差分数组。本质上a[N]就是s[N]的角色

a[1] = b[1]
a[2] = b[1] + b[2]
a[3] = b[1] + b[2] + b[3]
……
根据上面可以推出来:
b[1] = a[1]
b[2] = a[2] - b[1]
b[3] = a[3] - b[2]

核心:如果多次实现前缀和数组的[l, r]区间的元素 + c,可以借助拆分数组,因为拆分数组只要在a[l] + c,这样前缀和数组数组从l往后的元素都会 + c,因为ai = b1 + b2 .. +bi,这样的话,我还需要通过b[r + 1] - c,实现a[]数组r + 1以及往后的元素 - c的目的。

题目:
https://www.acwing.com/problem/content/799/

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 1e5 + 10;
  4. int n, m;
  5. int a[N], b[N];
  6. void insert(int l, int r, int c) { // 上方红色部分
  7. b[l] += c;
  8. b[r + 1] -= c;
  9. }
  10. int main() {
  11. scanf("%d%d", &n, &m);
  12. for (int i = 1; i <= n; i++) {
  13. scanf("%d", &a[i]);
  14. }
  15. for (int i = 1; i <= n; i++)
  16. insert(i, i, a[i]); // 构造b[]数组的时候,直接利用insert函数实现。bn = an - an - 1
  17. while (m--) {
  18. int l, r, c;
  19. scanf("%d%d%d", &l, &r, &c);
  20. insert(l, r, c); // 这里体现差分的好处,我对前缀和数组多次区间+c操作的时候,最后只要一个结果,这样只需要动b的两个元素,m越大优势自然越明显
  21. }
  22. for (int i = 1; i <= n; i++)
  23. b[i] += b[i - 1]; // 这里就是把b数组变成s数组。不过这个s数组就是计算多次+c操作
  24. for (int i = 1; i <= n; i++)
  25. printf("%d ", b[i]);
  26. return 0;
  27. }

四:差分矩阵

题目:
http://liuseroj.picp.io/problem/3209

原理:
本质上就是上面二维矩阵和和差分的一个思维拓展。
我计算一个二维矩阵 + c的时候,让b[x1][y1] + c,就是让a[x1][y1]开始右下角的全都加c,我只是要下图中的红色部分,因此我需要
b[x1][y2 +1] -= c,b[x2 + 1][y1] -= c,因为a[x2][y2]右下角的部分每个元素多减了一个c,所以需要b[x2 + 1][y2 + 1] += c
image.png

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 1010;
  4. int a[N][N], b[N][N];
  5. int x1, y1, x2, y2;
  6. int m, n, q;
  7. void insert(int x1, int y1, int x2, int y2, int c) {
  8. b[x1][y1] += c;
  9. b[x2 + 1][y2 + 1] += c;
  10. b[x1][y2 + 1] -= c;
  11. b[x2 + 1][y1] -= c;
  12. }
  13. int main() {
  14. scanf("%d%d%d", &n, &m, &q);
  15. for (int i = 1; i <= n; i++)
  16. for (int j = 1; j <= m; j++)
  17. scanf("%d", &a[i][j]);
  18. for (int i = 1; i <= n; i++)
  19. for (int j = 1; j <= m; j++)
  20. insert(i, j, i, j, a[i][j]);
  21. while (q--) {
  22. int c;
  23. scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
  24. insert(x1, y1, x2, y2, c);
  25. }
  26. for (int i = 1; i <= n; i++)
  27. for (int j = 1; j <= m; j++)
  28. b[i][j] = b[i][j - 1] + b[i - 1][j] - b[i - 1][j - 1] + b[i][j];
  29. for (int i = 1; i <= n; i++){
  30. for (int j = 1; j <= m; j++)
  31. cout << b[i][j] << " ";
  32. cout << endl;
  33. }
  34. return 0;
  35. }