C++算法竞赛内容,非专业内容。
本文仅为一维前缀和与差分,以后会更新二维的。
目录
引入
前缀和是一个神奇的东西,而差分就是前缀和的好兄弟。
这个美丽幽雅有趣的东西,CSP-J2022山东补赛确实考到了!
废话不多说,直接进入正题!
正文内容
一维前缀和与差分
1、一维前缀和
(1)一维前缀和的定义
首先,什么是前缀和?
不难理解,给定一个数组a,要求出每一位的前缀和,我们可以声明另一个数组b,b[i]的数值是a[1]+a[2]+a[3]+……+a[i]。这样,b[i]就是a的第1到i位的前缀和。
定义1.1 给定一个数列A,它的前缀和数列B的B[i]表示A从第1个元素到第i个元素的总和。
公式1.1 前缀和的表示公式。(不了解∑求和公式的可以搜索一下。)
一维前缀和,就是一维数组中的前缀和。
举个例子:
题目1.1 求出一维数组A的前缀和B。
其中给定A={3,4,7,8,-1,2}。
解:B={3,7,14,22,21,23}。
公式1.2 一维前缀和的递推公式(用s举例):
s[i]=s[i-1]+a[i]。
由此得出前缀和预处理代码:
for(int i=1;i<=n;i++){
b[i]=b[i-1]+a[i];
}
(2)利用一维前缀和求一维区间内元素的和
一维前缀和的应用主要有求一维区间内元素的和以及下面会讲到的差分等。
题目1.2 给定长度为n(n≤)的序列a,进行k(k≤)次询问,询问区间[l,r]之间的数据之和。
输入:n,k;a的所有元素;k次询问中的l,r。
输出:区间[l,r]的数据之和。
解法一:暴力枚举(显然TLE)
#include <iostream>
using namespace std;
int main(){
int a[100000],n,k;
cin >> n >> k;
for(int i=1;i<=n;i++){
cin >> a[i];
}
for(int i=1;i<=k;i++){
int l,r,sum=0;
cin >> l >> r;
for(int j=l;j<=r;j++){
sum+=a[j];
}
cout << sum << endl;
}
return 0;
}
两层循环嵌套,数据大了直接TLE!显然是不行。
解法二:利用一维前缀和
首先,如何利用一维前缀和求一维区间内元素的和?
如图1.1,可以粗略地理解为A=B-C。
假设N为原来的数列,数列B和C都是数列N的前缀和,a是数列A的第一个元素,那么可得:B[b]=N[1]+N[2]+…+N[a-1]+N[a]+N[a+1]+…+N[b],C[a-1]=N[1]+N[2]+…+N[a-1],那么两者之差就是N[a]+N[a+1]+…+N[b],不正是区间A的和吗?(自己推导一下。)
那么我们可得出结论(性质):
性质1.1 若有一维数列num,其前缀和为数列sum,那么对于num中的一维区间[l,r],其元素的和为sum[r]-sum[l-1]。
那么本题代码如下:
#include <iostream>
using namespace std;
int a[100000],b[100000],n,k;
int main(){
cin >> n >> k;
for(int i=1;i<=n;i++){
cin >> a[i];
b[i]=b[i-1]+a[i]; //递推公式,前缀和预处理
}
for(int i=1;i<=k;i++){
int l,r;
cin >> l >> r;
cout << b[r]-b[l-1] << endl; //公式:sum[r]-sum[l-1]
}
return 0;
}
时间复杂度:O(n+k)。
2、差分
先来一道例题(链接跳转):
题目1.3 点击此处查看题目
看一下 丧心病狂 善良的作者给我们的数据范围 (我自己出的题)。
没错,用暴力不超时就怪了!
解法一:暴力枚举(TLE)
由于不想打太简单了,就说一下思路:
声明一个数组,每次给区间[l,r]之间的数加1分,最后求出最大的元素。
解法二:差分
思考:
A={2,3,1,4,5},前缀和B={2,5,6,10,15}。
如果下标从1开始,把A[2]加上1,A[4]减去1,此时A={2,4,1,3,5},那么前缀和会怎么变化?
B={2,6,7,10,15}。
不难看出,B[2]到B[3]之间前缀和都加了1。
性质1.2 若有一维数组A,其前缀和为B,若A[l]+=c,A[r+1]-=c,那么此时B[l,r]之间各加上c。
因此,此题解法:
声明数组a,b,每次读入一组l,r,就让a[l]++,a[r+1]--,最后全部读入之后再求前缀和b,根据性质1.2,求出b的最大数就是本题答案。
举例:
3
2 4
3 5
1 4
进行如下操作:
a[2]++,a[5]--;
a[3]++,a[6]--;
a[1]++,a[5]--。
此时:a={1,1,1,0,-2,-1,…},b={1,2,3,3,1,0,…},答案为3。
请自己思考一下。
代码如下:
#include <biefuzhi>
using namespace std;
int m[1000100],n,Max,a,b,t;
int main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> a >> b;
m[a]++;
m[b+1]--;
}
for(int i=1;i<=1000000;i++){
m[i]+=t;
t=m[i];
if(t>Max) Max=t;
}
cout << Max;
return 0;
}
总结
一维前缀和与差分:(以下用s表示前缀和,a表示原数组)
前缀和:
定义:从s[1]加到s[i]。
递推公式:s[i]=s[i-1]+a[i]。
求区间[l,r]之间的数字和:sum[r]-sum[l-1]。
差分:若有一维数组A,其前缀和为B,若A[l]+=c,A[r+1]-=c,那么此时B[l,r]之间各加上c。