前缀和与差分简单介绍

前缀和

简介
前缀和擅长的是查询区间和,若要查询一个长度为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]组成部分
111 ∑ i = 1 1 a i \sum_{i=1}^{1}a_{i} i=11ai
212 ∑ i = 1 2 a i \sum_{i=1}^{2}a_{i} i=12ai
324 ∑ i = 1 3 a i \sum_{i=1}^{3}a_{i} i=13ai
415 ∑ i = 1 4 a i \sum_{i=1}^{4}a_{i} i=14ai
527 ∑ 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=12aii=15ai=5
即:
s u m 5 − s u m 2 = 5 sum_{5}-sum_{2}=5 sum5sum2=5
一般的,查询 i ~ j (i<j)的区间和等于
s u m j − s u m i − 1 sum_{j}-sum_{i-1} sumjsumi1

二维前缀和

生成方法:

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+sumi1,j+sumi,j1sumi1,j1
查询原理:
反向推导
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+sumi1,j+sumi,j1sumi1,j1

∑ 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,y2sumx11,y2sumx2,y11+sumx11,y11

前缀和例题

一维

最大连续子序列
内存限制:256 MiB
时间限制:1000 ms
题目描述
给定一个长度为N的整数序列 a i ( 1 ≤ i ≤ N ) a_{i(1≤i≤N)} ai(1iN) ,请你计算长度为 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} sumisumik
部分代码

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)。
解题思路

  1. 预处理:
    a. 用 s u m i , j sum_{i,j} sumi,j来存储第 i 行 j 列的价值总和。
    b. 将 5000 ∗ 5000 5000*5000 50005000的范围缩小,按输入的 x i , y i x_{i},y_{i} xi,yi的最大值来确定范围
  2. 使用前缀和:
    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=ir,l=jri,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
修改原理:
修改前

下标12345
原数组12345
差分数组11111

修改后

下标12345
原数组13455
差分数组12110

解释:
d i f 1 dif_{1} dif1除外,一般的 d i f i = a i − 1 − a i dif_{i}=a_{i-1}-a_{i} difi=ai1ai。所以,当 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} ai1的差增加 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+1c,即 a l a_{l} al a l − 1 a_{l-1} al1的差增加 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 ),表示一条指令。
输出格式
一个整数,表示中位数。
解题思路
用差分数组修改,再还原成原数组即可。

小结

这次关于前缀和与差分介绍就到此结束。前缀和与差分,是一种辅助工具,请大家好好理解运用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值