目录
1 前缀和
前缀和指数组或序列中每个位置之前的元素总和。
1.1 一维前缀和
原数组:a1 ,a2,a3…..an
前缀和:Si=a1+a2…..ai
求前缀和公式:s[i]=s[i-1]+a[i] ,s[0]=0;
序号i | 0 | 1 | 2 | 3 | 4 | 5 | |
原数组 | a[6] | 0 | 3 | 5 | 8 | 2 | 7 |
前缀和 | b[6] | 0 | 3 | 8 | 16 | 18 | 25 |
作用:求原数组[l,r]的和sum
使用原数组计算,则需要从下标l到下标r遍历原数组求和:
Sum=a[l]+a[l+1]…..+a[r];
使用前缀和计算:sum=b[r]-b[l-1]
1.2 一维前缀和案例
描述
输入n个整数,再输入m个区间,每个区间的起点下标L,终点下标R。对于每个区间,输出n个整数中从下标L到下标R的区间和。
输入描述
第一行包括两个整数n和m。(1≤n,m≤100000)
第二行包括n个整数。(1≤整数≤100)接下来有m行,每行包含两个整数L和R,表示区间范围。(0<L≤R≤n)
输出描述
输出有m行,每行一个整数,表示一个区间和。
样例输入
10 3
2 1 3 6 4 20 15 10 4 11
3 7
1 9
5 8
样例输出
48
65
49
代码实现
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, m ;
cin >> n >> m;
int a[n+1]={} , b[n+1]={} ,c[m+1][2]={},sum[m+1]={};
for(int i = 1;i <= n;i++)
{
cin >> a[i];
b[i]=b[i-1] + a[i];
}
for(int i = 1;i <=m; i++)
{
cin >>c[i][0] >>c[i][1];
sum[i]=b[c[i][1]]-b[c[i][0]-1];
cout<< sum[i] << endl;
}
return 0;
}
1.3 二维前缀和
一维前缀和是解决线性数组在某个区间内元素之和,二维前缀和是在平面问题上,放在二维数组中求矩阵的和。
原数组:a[1][1],a[1][2]……a[i][j]
a[i][j] | j=0 | j=1 | j=2 | j=3 | j=4 |
i=0 | 0 | 0 | 0 | 0 | 0 |
i=1 | 0 | 3 | 4 | 5 | 6 |
i=2 | 0 | 2 | 6 | 7 | 1 |
i=3 | 0 | 9 | 6 | 3 | 6 |
求前缀和:
s[i][j] | j=0 | j=1 | j=2 | j=3 | j=4 |
i=0 | 0 | 0 | 0 | 0 | 0 |
i=1 | 0 | 3 | 7 | 12 | 18 |
i=2 | 0 | 5 | 15 | 27 | 34 |
i=3 | 0 | 14 | 30 | 45 | 58 |
S[i][j]推导过程:
(1)i=0或j=0 前缀和默认为0;
(2)s[1][1]=3,s[1][2]=7,s[1][3]=12,s[1][4]=18;
推导过程:s[1][4]=s[1][3]+s[0][4]-s[0][3]+a[1][4]=12+0-0+6=18
即:s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+a[i][j]
(3)s[2][1]=5,s[2][2]=15
s[2][2]=s[1][2]+s[2][1]-s[1][1]+a[2][2]=7+5-3+6=15
求从[2,2]到[3,4]的面积= s[3][4]-s[3][1]-s[1][4]+s[1][2]
即从[x1,y1]到[x2,y2][]的面积=s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];
1.4 二维前缀和案例
描述
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
输入描述
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
输出描述
共 q 行,每行输出一个询问的结果。
样例输入
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
样例输出
17
27
21
数据范围与提示
1≤n,m≤1000,1≤q≤200000,1≤x1≤x2≤n,1≤y1≤y2≤m,−1000≤矩阵内元素的值≤1000
代码实现
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, m ,q;
cin >> n >> m >> q;
int a[n+1][m+1]={} ,sum[n+1][m+1]={},b[q+1][4]={},bj[q+1]={};
for (int i=1; i <=n ;i++)
{
for (int j=1; j <=m; j++)
{
cin >> a[i][j];
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
}
}
for (int i=1 ;i <=q; i++)
{
for (int j=0; j<4; j++)
{
cin>>b[i][j];
}
int x1=b[i][0];
int y1=b[i][1];
int x2=b[i][2];
int y2=b[i][3];
bj[i]=sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
cout<< bj[i]<<endl;
}
return 0;
}
2 差分
2.1 一维差分
差分相当于前缀和的逆运算:
原数组: a[1],a[2]…..a[n]
差分数组:b[1],b[2]…..b[n]
b[n]=a[n]-a[n-1]
求前缀和公式:s[i]=s[i-1]+a[i] ,s[0]=0;
序号 | i | 0 | 1 | 2 | 3 | 4 | 5 |
原数组 | a[6] | 0 | 3 | 5 | 8 | 2 | 7 |
差分数组 | b[6] | 0 | 3 | 2 | 3 | -6 | -5 |
作用:求原数组[L,R]的数全部加上c ,即a[L]+c,a[L+1]+c, a[R]+c,可以通过操作差分数组进行计算: 即,b[L]+c,b[R+1]-c
(1)原数组a在[1,3]区间加10,得到新数组c:
序号 | i | 0 | 1 | 2 | 3 | 4 | 5 |
原数组 | a[6] | 0 | 3 | 5 | 8 | 2 | 7 |
新组 | c[6] | 0 | 13 | 15 | 18 | 2 | 7 |
(2)c[6]对应差分数组d
序号 | i | 0 | 1 | 2 | 3 | 4 | 5 |
新组 | c[6] | 0 | 13 | 15 | 18 | 2 | 7 |
差分数组 | d[6] | 0 | 13 | 2 | 3 | -16 | -5 |
(3)新的差分数组d与原差分数组b的区别:d[1]-b[1]=10 ,d[4]-b[4]=10
因此:如果a在[1,3]区间加10,对应的差分数组b的变化:b[1]+10,b[10]-10
解题过程:
(1)初始化原数组a
(2)求原数组a的差分数组b
(3)求原数组[L,R]的数全部加上c,可以直接操作差分数组b::b[L]+c,b[R+1]-c
(4)根据差分数组b ,求对应的前缀和数组。
2.2 一维差分案例
描述
在一个桌子上摆放了 n 个杯子,每个杯子中有一定量的水。小 A 同学负责向杯子中倒水,他总共倒了 k 次,每次会向从第 L 个杯子到第 R 个杯子中添加 P 毫升的水(注意:水只可能增加,不可能减少)。请问小 A 同学倒了 k 次水之后, n 个杯子每个杯子有多少毫升的水。
输入描述
第一行包含两个整数 n 和 k。
第二行包含 n 个整数,表示一开始每个杯子中水的毫升数。
接下来 k 行,每行包含三个整数 L,R,P表示一次操作。
输出描述
共一行,包含 n 个整数,表示最终 n 个杯子每个杯子有多少毫升的水。
样例输入
8 3
1 2 10 8 1 5 1 1
7 8 12
1 8 4
2 3 12
样例输出
5 18 26 12 5 9 17 17
数据范围与提示
1≤n,k≤100000
1≤L≤R≤n1≤L≤R≤n,0≤P≤10000≤P≤1000
杯子中水的初始量在 [0,1000][0,1000] 的范围内。
本题数据上保证所有的杯子在加水之后,水量值任然在 int 范围内。
代码实现
#include<bits/stdc++.h>
using namespace std;
int a[100001]={}, b[100001]={} ,c[100001]={}, n, k;//a[]是原数组,b[]差分数组
int main(){
cin >> n >> k;
for(int i = 1;i <= n;i++)
{
cin >> a[i]; //是原数组
b[i] = a[i] - a[i-1]; //原数组的差分数组
}
for(int i = 1; i <= k; i++)
{
int l, r, p;
cin >> l >> r >> p;
//原数组l~r 加p,相当于时直接操作差分数组
b[l] = b[l] +p;
b[r+1] = b[r+1] -p;
}
for(int i = 1;i <= n;i++)
{
c[i]=b[i] + c[i-1];//求差分数组的前缀和数组即为原数组
cout <<c[i] <<" ";
}
return 0;
}
2.3 二维差分
原数组:a[1][1],a[1][2]……a[i][j]
a[i][j] | j=0 | j=1 | j=2 | j=3 | j=4 |
i=0 | 0 | 0 | 0 | 0 | 0 |
i=1 | 0 | 3 | 4 | 5 | 6 |
i=2 | 0 | 2 | 6 | 7 | 1 |
i=3 | 0 | 9 | 6 | 3 | 6 |
差分数组:b[1][1],b[1][2]……b[i][j]
b[i][j] | j=0 | j=1 | j=2 | j=3 | j=4 |
i=0 | 0 | 0 | 0 | 0 | 0 |
i=1 | 0 | 3 | 1 | 1 | 1 |
i=2 | 0 | -1 | 3 | 0 | -7 |
i=3 | 0 | 7 | -7 | -4 | 9 |
b[i][j]推导过程:b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]
(1)i=0或j=0 前缀和默认为0;
b[1][1]=3,b[1][2]=1,b[1][3]=1,b[1][4]=1;
推导过程:b[1][4]=a[1][4] -a[0][4]-a[1][3] +s[0][0] =6-5-0+0=6
(2)s[2][1]=2,s[2][2]=15
s[2][2]=a[2][2]-a[1][2]-a[2][1]+a[1][1]=6-4-2+3=3
差分数组应用 :求原数组[1,2] 和[2,3]直接的全部数加上10 ,得到新数组c:
通过差分数组b计算推导过程:
(1)a对应差分数组b
(2)c对应差分数组d
(3)新的差分数组d与原差分数组b的区别:
d[1][2]-b[1][2]=10 ,即d[x1][y1]=b[x1][y1]+c;
d[3][2]-b[3][2]=-10,即d[x2+1][y1]=b[x2+1][y1]-c;
d[1][4]-b[1][4]=-10,即d[x1][y2+1]=b[x1][y1+2]-c;
d[2][3]-b[2][3]=10,即d[x2+1][y2+1]=b[x2+1][y2+1]+c;
(4)根据差分数组d,求前缀和数组s,即与原数组a在 [1,2] 和[3,4]直接的全部数加上10后的数组c 相同
s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+d[i][j]
解题过程:
(1)初始化原数组a
(2)求原数组a的差分数组b
(3)求原数组[x1,y1]和[x2,y2]的数全部加上c,可以直接操作差分数组b:
d[x1][y1]=b[x1][y1]+c;
d[x2][y1]=b[x2+1][y1]-c;
d[x1][y2]=b[x1][y2+1]-c;
d[x2][y2]= d[x2+1][y2+1]=b[x2+1][y2+1]+c;
(4)根据差分数组b ,求对应的前缀和数组。
2.4 二维差分案例
描述
有一个 n×n 大小的方阵,矩阵中的初始值为 0 ,有 k 次操作,每次会将矩阵中以 x1, y1 和 x2 , y2 为左上角和右下角的子矩阵中的每个数加 1。请输出经过 k 次加数操作后,方阵中每个元素的值。
输入描述
第 1 行有两个整数 n 和 k。(n,k ≤ 1000)
接下来 k 行,每行有 4 个整数 x1 , y1 和 x2 , y2,两点坐标均在方阵范围内,且左上角的坐标 ≤ 右下角的坐标。
输出描述
输出经过 k 次操作后,方阵中每个元素的值。
样例输入
5 3
2 2 3 3
3 3 5 5
1 2 1 4
样例输出
0 1 1 1 0
0 1 1 0 0
0 1 2 1 1
0 0 1 1 1
0 0 1 1 1
代码实现
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, k , b[1001][1001]={},s[1001][1001]={},q[1001][4]={};
cin >> n >> k ;
for (int i=1 ;i <=k; i++)
{
for (int j=0; j<4; j++)
{
cin>>q[i][j];
}
int x1=q[i][0];
int y1=q[i][1];
int x2=q[i][2];
int y2=q[i][3];
b[x1][y1]+=1;
b[x1][y2+1]-=1;
b[x2+1][y1]-=1;
b[x2+1][y2+1]+=1;
}
for (int i=1; i <=n ;i++)
{
for (int j=1; j <=n; j++)
{
s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+b[i][j];
cout << s[i][j] <<" ";
}
cout<<endl;
}
return 0;
}