基础算法 前缀和与差分
基础算法 前缀和与差分
学习地址:AcWing
一、前缀和
一维前缀和
原数组:\(a_1、a_2、a_3、a_4、a_5.....a_n\)
前缀和:\(S_i=a_1+a_2+a_3+...+a_i\),\(S_0=0\) 从1开始,为了处理边界问题
求区间和:\(S_{l-r}=S_r-S_{l-1}\)
求\(S_i\):可以同样用区间和求前缀和:\(S_i=S_i-S_0\),如\(S_{10}=S_{10}-S_0\)
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i]; // 前缀和的初始化
while (m--) {
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", s[r] - s[l - 1]); // 区间和的计算
}
二维前缀和
容斥原理的思想
二维原数组 \(a_{11},a_{12},,,,a_{1m}\)
\(a_{21},a_{22},,,,a_{2m}\)
,,,
\(a_{n1},a_{n2},,,,a_{nm}\)
前缀和:\(S_{ij}=S_{i-1j}+S_{ij-1}-S_{i-1j-1}+a_{ij}\)
区间和:\(S[x_1\sim x_2,y_1\sim y_2]=\)\(S_{x_2y_2}-S_{x_2y_1-1}-S_{x_1-1y2}+S_{x_1-1y_1-1}\)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]; // 前缀和
}
}
while (q--) {
int x1, x2, y1, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]); // 区间和、部分和、子矩阵的和
}
二、差分
差分为前缀和的逆运算
假设函数\(S=f(x)\) ,为求x
的前缀和,则\(a = f(b)\),\(b = f^{-1}(a)\)
一维差分
区间修改\(O(n)\)
数组a
:\(a_1,a_2,a_3,,,,,,a_n\) (前缀和)
构造数组b
:\(b_1,b_2,b_3,,,,,,b_n\) (差分)
使得:a
为b
的前缀和:\(a_i=b_1+b_2+b_3+,,,+b_i\),b
称为a
的差分,对b
求一下前缀和得到a
满足:\(b_1=a_1\),\(b_2=a_2-a_1\),\(b_3=a_3-a_2\),,,,\(b_n=a_n-a_{n-1}\)
对a
的一些操作,比如在a
的[l~r]
区间全部数加上一个c
,只需要在差分b
里的b[l]
加上c
和b[r+1]
减去c
,最后再求解前缀和就可以。
#include <iostream>
using namespace std;
const int N = 1000010;
int n, m;
int a[N], b[N];
// 求差分数组、插入部分可用此函数代替,insert(i, i, a[i]) insert(l, r, c)
void insert(int l, int r, int c) {
b[l] += c;
b[r + 1] += c;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i++) b[i] = a[i] - a[i - 1]; // 求差分数组
while (m--) {
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
b[l] += c, b[r + 1] -= c; // 插入
}
for (int i = 1; i <= n; i++) {
b[i] += b[i - 1]; // 求前缀和
}
for (int i = 1; i <= n; i++) {
printf("%d ", b[i]);
}
return 0;
}
二维差分
容斥原理的思想
二维原数组a
\(a_{11},a_{12},,,,a_{1m}\) 构造差分数组b
\(b_{11},b_{12},,,,b_{1m}\)
\(a_{21},a_{22},,,,a_{2m}\) \(b_{21},b_{22},,,,b_{2m}\)
,,, ,,,
\(a_{n1},a_{n2},,,,a_{nm}\) \(b_{n1},b_{n2},,,,b_{nm}\)
使得a
为b
的前缀和数组,b
为a
的差分数组
对a
的[x1,y1~x2,y2]
全部数加上c
,等价于:
-
\(b_{x_1,y_1} += c\) 整个右下角全部加上
c
-
\(b_{x_1,y_2+1} -= c\) 下面的部分
-
\(b_{x_2+1,y_1} -= c\) 右面的部分
-
\(b_{x_2+1,y_2+1} += c\) 加上重复减去的右下角
而初始的数组,a[i,j]
相当于对a
的[i,j~i,j]
加上a[i,j]
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int a[N][N], b[N][N];
void insert(int x1, int y1, int x2, int y2, int c) {
b[x1][y1] += c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main() {
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
insert(i, j, i, j, a[i][j]); // 初始化差分数组
}
}
while (q--) {
int x1, x2, y1, y2, c;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, y2, c); // 继续构造差分
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1]; // 计算前缀和
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
printf("%d ", b[i][j]); // 输出
}
printf("\n");
}
return 0;
}