前缀和 & 差分¶
前缀和¶
简介¶
前缀和可以简单理解为「数列的前\(n\)的和」,是一种重要的预处理方式,能大大降低查询的时间复杂度。
一维前缀和¶
Code¶
实在没啥可说的,代码:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m, l, r;
int q[N], s[N];
int main()
{
cin >> n >> m;
q[0] = 0, s[0] = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d", &q[i]);
s[i] = s[i - 1] + q[i];
}
while (m--)
{
scanf("%d%d", &l, &r);
printf("%d\n", s[r] - s[l - 1]);
}
return 0;
}
二维前缀和¶
Method¶
求矩阵的和:\(\operatorname{sum}_{i, j} = \operatorname{sum}_{i-1, j}+\operatorname{sum}_{i, j-1}+\operatorname{a}_{i, j} - \operatorname{sum}_{i - 1, j-1}\)
求某个子矩阵的和:\(\operatorname{sum}_{x 2, y 2}-\operatorname{sum}_{x 1-1, y 2}-\operatorname{sum}_{x 2, y 1-1}+\operatorname{sum}_{x 1-1, y 1-1}\)
Code¶
#include <iostream>
using namespace std;
const int N = 1e3 + 10;
int n, m, k;
int x1, y1;
int x2, y2;
int q[N][N], s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &q[i][j]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j] = q[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
while (k--)
{
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]);
}
return 0;
}
差分¶
简介¶
差分是一种和前缀和相对的策略,可以当做是求和的逆运算。
这种策略的定义是令\(b_{i}=\left\{\begin{array}{ll} a_{i}-a_{i-1} & i \in[2, n] \\ a_{1} & i=1 \end{array}\right.\)
本质上,差分就是前缀和的逆运算。
一维差分¶
Method¶
所谓差分,其实就是前缀和的逆运算 有这么一个数组a: a1, a2, ..., an 定义数组b为: b1 = a1 b2 = a2 - a1 b3 = a3 - a2 ... 那么此时b就是a的差分。
仔细看不难发现 ai = b1 + b2 + ... + bi 所以a也就是b的前缀和。即可以看出前缀和和差分就是逆运算。
那么差分有什么用?¶
一般解决这样的插入问题:给定一个区间[l, r],现在要对该区间所有值+C。 正常做法就是o(n)的每一个加一遍 有了差分,我们只需要将(b[l] + c)然后(b[r] - c)即可。
为什么呢?¶
当b[l] + c,会发生什么?a_l, a_(l+1), ..., a_n所有数全部加C了 因为使用查分还原原数组的时候,做前缀和,会让这个C对l点以后的值都起作用 那么同理,你想要指定这个区间,把(l+1)点-C就可以不再让r点后的值不在受影响了
技巧¶
差分数组要构造吗?即对a数组o(n)的跑一遍构造b。 其实可以不做,我们可以把a数组想成全是0,那么b也就是0 然后a数组实际每一个值,就是b数组一次插入操作:b[i] + c , b[i + 1] - c。 直接当做插入操作来处理就行了
Code¶
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], d[N];
void difference_insert(int l, int r, int c)
{
d[l] += c;
d[r + 1] -= c;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
difference_insert(i, i, a[i]);
}
int l, r, c;
while (m--)
{
scanf("%d%d%d", &l, &r, &c);
difference_insert(l, r, c);
}
for (int i = 1; i <= n; i++) a[i] = a[i - 1] + d[i];
for (int i = 1; i <= n; i++) printf("%d ", a[i]);
printf("\n");
return 0;
}
二维差分¶
Code¶
//
// 二维差分其实根本不需要想太多,因为查分就是前缀和的逆运算。直接推出来就行。
//
#include <iostream>
using namespace std;
const int N = 1010;
int n, m, q;
int d[N][N], a[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
d[x1][y1] += c;
d[x2 + 1][y2 + 1] += c;
d[x1][y2 + 1] -= c;
d[x2 + 1][y1] -= 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]);
}
int x1, x2, y1, y2, c;
while(q--)
{
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++)
{
a[i][j] = d[i][j] + a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}