前缀和
简介
前缀和擅长的是查询区间和,若要查询一个长度为n的序列中的长k的子序列和,前缀和的查询速度是
O
(
1
)
O(1)
O(1),而不用前缀和的方法的时间复杂度是
O
(
k
)
O(k)
O(k)
一维前缀和
生成方法:
int n;
cin>>n;
int a[n+1],sum[n+1];
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++) {
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
查询方法:
int l,r;
cin>>l>>r;
cout<<sum[r]-sum[l-1];
举个例子:
输入:
5 3
1 1 2 1 2
3 5
输出:
5
查询原理:
下标(i) | a[i] | sum[i] | 组成部分 |
---|---|---|---|
1 | 1 | 1 | ∑ i = 1 1 a i \sum_{i=1}^{1}a_{i} ∑i=11ai |
2 | 1 | 2 | ∑ i = 1 2 a i \sum_{i=1}^{2}a_{i} ∑i=12ai |
3 | 2 | 4 | ∑ i = 1 3 a i \sum_{i=1}^{3}a_{i} ∑i=13ai |
4 | 1 | 5 | ∑ i = 1 4 a i \sum_{i=1}^{4}a_{i} ∑i=14ai |
5 | 2 | 7 | ∑ i = 1 5 a i \sum_{i=1}^{5}a_{i} ∑i=15ai |
若查询 3 ~ 5 的区间内的和
则有下方式:
∑
i
=
3
5
a
i
=
∑
i
=
1
2
a
i
−
∑
i
=
1
5
a
i
=
5
\sum_{i=3}^{5}a_{i}=\sum_{i=1}^{2}a_{i}-\sum_{i=1}^{5}a_{i}=5
∑i=35ai=∑i=12ai−∑i=15ai=5
即:
s
u
m
5
−
s
u
m
2
=
5
sum_{5}-sum_{2}=5
sum5−sum2=5
一般的,查询 i ~ j (i<j)的区间和等于
s
u
m
j
−
s
u
m
i
−
1
sum_{j}-sum_{i-1}
sumj−sumi−1
二维前缀和
生成方法:
int n,m;
cin>>n>>m;
int a[n+1][m+1],sum[n+1][m+1];
memset(a,0,sizeof(a));
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
cin>>a[i][j];
sum[i][j]=a[i][j]+sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1];
}
查询方法:
查询从左上角(
x
1
,
y
1
x_{1},y_{1}
x1,y1)坐标到右下角(
x
2
,
y
2
x_{2},y_{2}
x2,y2)坐标的
矩形区域(
x
1
x_{1}
x1 ~
x
2
x_{2}
x2,
y
1
y_{1}
y1 ~
y
2
y_{2}
y2)内的所有数字之和。
int x1,x2,y1,y2;
cin>>x1>>y1>>x2>>y2;
cout<<sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
举个例子:
输入:
3 3
0 0 1
1 1 0
1 0 1
2 1 2 3
输出:
2
生成原理:
假设
i
=
3
,
j
=
3
i=3,j=3
i=3,j=3
s
u
m
3
,
3
=
sum_{3,3}=
sum3,3=
(1,1)(1,2)(1,3)
(2,1)(2,2)(2,3)
(3,1)(3,2)(3,3)
=
a
3
,
3
=a_{3,3}
=a3,3
(1,1)(1,2)(1,3)
(2,1)(2,2)(2,3)
(3,1)(3,2)(3,3)
+
s
u
m
2
,
3
+sum_{2,3}
+sum2,3
(1,1)(1,2)(1,3)
(2,1)(2,2)(2,3)
(3,1)(3,2)(3,3)
+
s
u
m
3
,
2
+sum_{3,2}
+sum3,2
(1,1)(1,2)(1,3)
(2,1)(2,2)(2,3)
(3,1)(3,2)(3,3)
−
s
u
m
2
,
2
-sum_{2,2}
−sum2,2
(1,1)(1,2)(1,3)
(2,1)(2,2)(2,3)
(3,1)(3,2)(3,3)
所以
s
u
m
i
,
j
=
a
i
,
j
+
s
u
m
i
−
1
,
j
+
s
u
m
i
,
j
−
1
−
s
u
m
i
−
1
,
j
−
1
sum_{i,j}=a_{i,j}+sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}
sumi,j=ai,j+sumi−1,j+sumi,j−1−sumi−1,j−1
查询原理:
反向推导
s
u
m
i
,
j
=
a
i
,
j
+
s
u
m
i
−
1
,
j
+
s
u
m
i
,
j
−
1
−
s
u
m
i
−
1
,
j
−
1
sum_{i,j}=a_{i,j}+sum_{i-1,j}+sum_{i,j-1}-sum_{i-1,j-1}
sumi,j=ai,j+sumi−1,j+sumi,j−1−sumi−1,j−1
得
∑
i
=
x
1
,
j
=
y
1
x
2
,
y
2
a
i
,
j
=
s
u
m
x
2
,
y
2
−
s
u
m
x
1
−
1
,
y
2
−
s
u
m
x
2
,
y
1
−
1
+
s
u
m
x
1
−
1
,
y
1
−
1
\sum_{i=x_{1},j=y_{1}}^{x_{2},y_{2}}a_{i,j}=sum_{x_{2},y_{2}}-sum_{x_{1}-1,y_{2}}-sum_{x_{2},y_{1}-1}+sum_{x_{1}-1,y_{1}-1}
∑i=x1,j=y1x2,y2ai,j=sumx2,y2−sumx1−1,y2−sumx2,y1−1+sumx1−1,y1−1
前缀和例题
一维
最大连续子序列
内存限制:256 MiB
时间限制:1000 ms
题目描述
给定一个长度为N的整数序列
a
i
(
1
≤
i
≤
N
)
a_{i(1≤i≤N)}
ai(1≤i≤N) ,请你计算长度为 K 的最大连续子序列。
注意:这里的长度为 K,表示连续子序列的元素个数为 K,这里的最大是指 K 个元素的和最大。
输入格式
第一行包含两个整数:N,K。
接下来的一行,共 N 个整数,表示给定的整数序列。
输出格式
一个整数,表示长度为 K 的最大连续子序列的和。
解题思路
因为他是连续的,我们就可以寻找第i(K<i≤N)位向前数K位的数字和的最大值,即
s
u
m
i
−
s
u
m
i
−
k
sum_{i}-sum_{i-k}
sumi−sumi−k
部分代码
int n,k,ans=-2e10,a[1e4+5],sum[1e4+5];
int main () {
cin>>n>>k;
for(int i=1;i<=n;i++) {
cin>>a[i];
b[i]=a[i]+b[i-1];
}
for(int i=k+1;i<=n;i++)
ans=max(ans,sum[i]-sum[i-k]);
cout<<ans;
return 0;
}
二维
激光炸弹
内存限制:256 MiB
时间限制:1000 ms
题目描述
一种新型的激光炸弹,可以摧毁一个边长为r的正方形内的所有的目标。现在地图上有n(n<=10000)个目标,用整数
x
i
,
y
i
x_{i},y_{i}
xi,yi(其值 在[1,5000])表示目标在地图上的位置,每个目标都有一个价值(
v
i
v_{i}
vi)。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。若目标位于爆破正方形的边上,该目标将不会被摧毁。
输入格式
输入文件的第一行为正整数n和正整数R,接下来的n行每行有3个正整数,分别表示
x
i
,
y
i
,
v
i
x_{i},y_{i},v_{i}
xi,yi,vi
输出格式
输出文件仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过32767)。
解题思路
- 预处理:
a. 用 s u m i , j sum_{i,j} sumi,j来存储第 i 行 j 列的价值总和。
b. 将 5000 ∗ 5000 5000*5000 5000∗5000的范围缩小,按输入的 x i , y i x_{i},y_{i} xi,yi的最大值来确定范围 - 使用前缀和:
a. 从 ( 1 , 1 ) (1,1) (1,1)开始统计 ∑ k = 1 , l = 1 i , j a k , l \sum_{k=1,l=1}^{i,j}a_{k,l} ∑k=1,l=1i,jak,l的值
b. 从 ( r , r ) (r,r) (r,r)处开始寻找, ∑ k = i − r , l = j − r i , j a k , l \sum_{k=i-r,l=j-r}^{i,j}a_{k,l} ∑k=i−r,l=j−ri,jak,l的最大值
部分代码
scanf("%d %d" ,&n ,&r);
maxx=r,maxy=r;//优化范围
while(n--) {
scanf("%d %d %d" ,&x ,&y ,&v);
sum[x][y]+=v,maxx=max(maxx,x),maxy=max(maxy,y);//统计价值,优化范围
}
for(int i=1;i<=maxx;i++) //计算前缀和
for(int j=1;j<=maxy;j++)
sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
for(int i=r;i<=maxx;i++) //寻找最大值
for(int j=r;j<=maxy;j++)
ans=max(ans,sum[i][j]-sum[i-r][j]-sum[i][j-r]+sum[i-r][j-r]);
printf("%d",ans);
差分
差分简介
差分主要用来修改区间元素值,若要使一个长度为n的序列中的长k的子序列元素值同时加减,差分的修改速度是
O
(
1
)
O(1)
O(1),而挨个修改的方法的时间复杂度是
O
(
k
)
O(k)
O(k)
生成方法:
int n;
cin>>n;
int a[n+1],dif[n+1];
memset(a,0,sizeof(a));
memset(sum,0,sizeof(dif));
for(int i=1;i<=n;i++) {
cin>>a[i];
dif[i]=a[i-1]-a[i];
}
修改方法:
int l,r,c;
cin>>l>>r>>c;
dif[l]+=c,dif[r+1]-=c;
举个例子:
输入:
5
1 2 3 4 5
2 4 1
输出:
1 3 4 5 5
修改原理:
修改前
下标 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
原数组 | 1 | 2 | 3 | 4 | 5 |
差分数组 | 1 | 1 | 1 | 1 | 1 |
修改后
下标 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
原数组 | 1 | 3 | 4 | 5 | 5 |
差分数组 | 1 | 2 | 1 | 1 | 0 |
解释:
除
d
i
f
1
dif_{1}
dif1除外,一般的
d
i
f
i
=
a
i
−
1
−
a
i
dif_{i}=a_{i-1}-a_{i}
difi=ai−1−ai。所以,当
a
i
+
=
c
a_{i}+=c
ai+=c时,
d
i
f
i
+
=
c
,
d
i
f
i
+
1
−
=
c
dif_{i}+=c,dif_{i+1}-=c
difi+=c,difi+1−=c,即
a
i
a_{i}
ai与
a
i
−
1
a_{i-1}
ai−1的差增加
c
c
c,
a
i
a_{i}
ai与
a
i
+
1
a_{i+1}
ai+1的差减少
c
c
c。
类似的,当
a
l
+
c
,
a
l
+
1
+
c
,
⋅
⋅
⋅
,
a
r
+
c
a_{l}+c,a_{l+1}+c,···,a_{r}+c
al+c,al+1+c,⋅⋅⋅,ar+c时,
d
i
f
l
+
c
,
d
i
f
r
+
1
−
c
dif_{l}+c,dif_{r+1}-c
difl+c,difr+1−c,即
a
l
a_{l}
al与
a
l
−
1
a_{l-1}
al−1的差增加
c
c
c,
a
r
a_{r}
ar与
a
r
+
1
a_{r+1}
ar+1的差减少
c
c
c。
差分还原:
在差分数组修改完成后,往往要将其还原成原序列。
我们求差分数组的前缀和即可还原修改后的数组。
也可以有这样的等价式:原数组<=>差分数组的前缀和<=>前缀和数组的差分数组
差分例题
叠干草
内存限制:256 MiB
时间限制:1000 ms
题目描述
有 N (为奇数)堆干草,按 1…N 编号,开始时每堆高度都是 0。
FJ给出 K 条指令,每条指令包含两个用空格隔开的整数,例如 “10 13 ”,表示给 10,11,12,13 这四堆干草分别叠加一捆干草,即高度均增加1。
FJ想知道,干草堆完后,这 N 堆干草高度的中位数(即干草堆完后并排完序后干草堆中间的干草数量)是多少。
输入格式
第 1 行:两个整数,分别是 N 和 K。
第 2…K+1 行:每行两个整数 A 和 B(1 ≤ A ≤ B ≤ N ),表示一条指令。
输出格式
一个整数,表示中位数。
解题思路
用差分数组修改,再还原成原数组即可。
小结
这次关于前缀和与差分介绍就到此结束。前缀和与差分,是一种辅助工具,请大家好好理解运用。