如何求出某一段区间内的和?——这里可以使用前缀和
如果有一个长度为n的数组:a1,a2…an
前缀和:Si = a1+a2+…+ai
1、如何求Si
S0 = 0;
for(i = 1; i <= n; ++i)
{
S[i] = S[i - 1] + ai;
}
这里for循环是从1开始的,原因是为了保证计算S10 - S0 = 1 - 10区间内的和
2、前缀和的作用
快速求出原数组一段数的和[l,r]
Sr - Sl-1
Sr = a1 + a2 + … + al-1 + al + … + ar
Sl-1 = a1 + a2 + … + al-1
比如求[1,10]的和也就是求S10 - S0
就把S0定为0,此时就是S10
#include
#include
using namespace std;
const int N = 100010;
int arr[N], sum[N];
int main()
{
int m,n,x;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
{
cin >> x;
sum[i] = sum[i - 1] + x; // 前缀和数组
}
while (m--)
{
int l, r;
scanf("%d%d", &l, &r);
cout << sum[r] - sum[l - 1] << endl;
}
return 0;
}
拓展:
ios::sync_with_stdio(false); // 提高cin的读取速度——不能在使用scanf了
数据规模>一百万建议使用scanf
二维前缀和S[i,j]的含义:
S[i,j] = S[i-1,j] + S[i,j-1] - S[i-1,j-1] + a[i,j]
以(x1,y1)为左上角,(x2,y2)为右下角的子矩形所有数的和该如何计算
S(x2,y2,x1,y1) = S(x2,y2) - S(x1-1,y2) - S(x2,y1-1) + S(x1-1,y1-1)
注意:aij表示以i,j坐标为顶点,单位为1的小矩阵,也就是坐标对应的是一个小方格,而非点
因此在二维矩阵中,算一个小矩阵的面积模板为:
Sij = ?
for (i~n)
{
for(j~m)
{
Sij = Si-1,j + Si,j-1 - Si-1,j-1 + aij;
}
}
注意:
前缀和一般for循环是从1开始,因为会涉及i-1,j-1
子矩阵的和
#include
using namespace std;
const int N = 1010;
int sum[N][N], arr[N][N];
int main()
{
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
scanf("%d", &arr[i][j]);
}
}
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + arr[i][j]; // 求出前缀和
}
}
while (q--)
{
int x1, x2, y1, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
cout << sum[x2][y2] - sum[x1 - 1][y2] - sum[x2][y1- 1] + sum[x1 - 1][y1 - 1] << endl; // 求子矩阵的和
}
return 0;
}
差分实际上是前缀和的逆运算
有一个数组:a1,a2,…,an
构造一个B数组:b1,b2,…,Bn
使得ai = b1 + b2 + … bi——a数组是b数组的前缀和
一维构造:
b1 = a1
b2 = a2 - a1
b3 = a3 - a2
…
bn = an - an-1
证明:
a1 = b1
a2 = b2 + b1
a3 = b3 + b2 + b1
因此只要有b数组,通过前缀和运算,就可以在O(n)时间内得到a数组
b数组称为a的差分,a称为b的前缀和
差分的作用:
在一个区间[l,r]内,要使得a数组的每一个值都加上一个c,使得a(l) + c ,a(l+1) + c ,…,a® + c时间复杂度就是O(n),用差分就可以达到O(1)
for(l~r)
{
a[i] + c;
}
对于b数组某一个位置修改,就会影响到a数组从这个位置开始后面每一个位置的修改——因为b数组的前缀和就是a数组
让b数组中b(l)+c,a数组就会受到影响:
a(l) + c, a(l + 1) + c … a(n) + c ——因为a(l)=b1+…+bl ,a(l+1) = b1 + … +b(l+1),a数组后续都会受到影响
然后打一个补丁:b(r+1) - c
这样子,就保证了不会影响l,r区间内的数,因此得出一维差分的结论:
a数组[l,r]区间的每一个数加上一个常数c,只需要让b数组做
b[l] += c; b[r+1] -= c; 时间复杂度为O(1)
因此只需要O(1)的时间给一个数组加上一个给定的值
假定a数组原本都是0,这样b数组所有也都是0
但是a数组是有值的,因此可以假定进行了n次插入操作,也就是a数组第一个数为[1,1] + a1,第二个数就是[2,2]+a2
#include
#include
using namespace std;
const int N = 100010;
int n, m;
int a[N], b[N];
void insert(int l, int r, int c) // [l, r]这段区间+c
{
// 构造差分数组b
b[l] += c; // b[l]+c后,a[l]及其之后的所有数都加上了c
b[r + 1] -= c; // 这样子a[r+1]及其之后的所有数都减去了c
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i)
{
insert(i, i, a[i]); // 初始ab数组都是0,b是a的差分数组,但是a数组填入数后,为了保证b仍然是a的差分,所以这里要insert
}
while (m--)
{
int l, r, c;
cin >> l >> r >> c;
insert(l, r, c);
}
for (int i = 1; i <= n; ++i)
{
a[i] = a[i - 1] + b[i]; // a数组就是b数组的前缀和
}
for (int i = 1; i <= n; ++i)
{
cout << a[i] << " ";
}
return 0;
}
#include
using namespace std;
const int N = 1010;
int a[N][N], b[N][N];
void insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main()
{
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
scanf("%d", &a[i][j]);
}
}
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
insert(i, j, i, j, a[i][j]); // 构造差分矩阵b
}
}
while (q--)
{
int x1, y1, x2, y2, c;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &c);
insert(x1, y1, x2, y2, c);
}
// 求b数组的二维前缀和
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j]; // a数组就是b数组的前缀和,这样子就求出a数组
}
}
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
printf("%d ", a[i][j]);
}
cout << endl;
}
return 0;
}