差分、前缀和有着特殊的关系,也是一种入门算法
首先考虑这样一个问题:
有 N 个的正整数放到数组 A 里,现在要求一个新的数组 B,新数组的第 i 个数 B[i]是原数组 A 第 0 到第 i 个数的和。
前缀和
前缀和想必大家都知道,给定一数组c[i]
,用另外一数组sum[n]
来存储c[1]-c[n]
的和,这样在求任意区间的和的时候,就非常的快捷了
具体递推为
sum[i] = c[i-1]+c[i]
差分
什么是差分?
差分是两个元素的差值,我们用一个新的数组a[i]
记录差分的值,数组 a 叫做 差分数组
具体递推为
a[i] = c[i]-c[i-1]
差分和前缀和有什么关系呢
前缀和数组sum[]
差分数组a[]
差分数组a[]
的前缀和是原数组
前缀和sum[]
的差分数组是原数组
在实际的应用当中,对一个数组进行操作时,进行区间增加或者减少同样的数值,就可以直接在差分数组上进行修改,下次在查询某个点的值就可以通过差分数组的来求,十分的快捷
二维前缀和
现在有这样一个问题
给定一个n*m大小的矩阵a,有q次询问,每次询问给定x1,y1,x2,y2四个数,求以(x1,y1)为左上角坐标和(x2,y2)为右下角坐标的子矩阵的所有元素和,包括左上角和右下角的元素。
像这样的题目,我们首先想到的就是遍历求和,但是如果时多组输入的话,每次都一遍一遍的计算,很容易TLE,所以我们可以在原数组的基础上构造二维前缀和数组,求解的话就可以比较快了
如何构造二维前缀和数组?
二维数组的每个点等于其左边的点+上边的点-左上方的点
,如果存在就计算,不存在则不计算,比如在边界上~
在这个图里,a = b+c-d + a
因为 b 和 c 中都加了一遍d,所以根据容斥原理,把多加的减掉
假如我想求a[2][4]的前缀和,我得先加上a[1][4]的前缀和,再加上a[2][3]的前缀和,然后这个时候我们发现实际上a[1][3]这个部分我们加了两遍,所以我们需要再减去一遍a[1][3],于是得出公式a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1]。
完整代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+9;
int a[maxn][maxn];
int main(){
int i,j,k,n,m,q;
cin>>n>>m>>q;
for(i=1;i<=n;i++){
for(j=1;j<=m;j++)
cin>>a[i][j];
}
for(i=1;i<=n;i++){
for(j=1;j<=m;j++)
a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];
}
for(i=1;i<=q;i++){
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
int ans=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];
cout<<ans<<endl;
}
}
例题
最高的牛
题意很简单,我们可以知道一点,在给出的相互能看见的牛对形成的区间是不会交叉的,因为每对牛中间的牛都比他们低
思路:如果两头牛看得见则中间的牛h-1,同时使用差分记录,最后再前缀和还原数组。
#include <iostream>
using namespace std;
const int N = 1e4+10;
int s[N]; //记录差分
bool flag[N][N]; //此处用int会超内存 int是4 个字节32位,bool只有1位
int main()
{
int m, n, p, h;
cin >> n >> p >> h >> m;
int a, b;
s[1] = h;
for (int i = 1; i <= m; i++)
{
cin >> a >> b;
if (a > b)
swap(a, b);
if (!flag[a][b])
{
s[a + 1]--;
s[b]++;
flag[a][b] = 1;
}
}
for (int i = 1; i <= n; i++)
{
s[i] = s[i - 1] + s[i];
cout << s[i] << endl;
}
return 0;
}