话说有个喜欢信息学竞赛的小白,今天刷题的时候遇见一道自己怎么写就是过不了的题
乍一看这不就是先把每个数存在一维数组里,每次询问就暴力地做一次循环这么简单的事而已。
教练呵呵一笑
他再仔细看题,这最多可能有10000个数,1000次询问;时间复杂度太高了!
教练就交给他了一种又快又帅的算法-------一维前缀和与差分!!!!!!!!
一维前缀和和差分
预处理
简单介绍一下,前缀和是一种重要的预处理,能大大降低查询的时间复杂度。可以看做动态规划的一种。简单理解就是“数列的前N项和”,差分即是作差,是以前缀和预处理为基础的。可以说两者是相辅相成的关系。
这个优化主要是用来在O(1)时间内求出一个序列中a[i]+a[i+1]+……+a[j]的和
预处理的转移方程给出 f[i+1]=f[i]+输入的数
f[x] 表示输入的第一个数到第x个数的和
可能还是不明不白,没挂系。且听我给你解释
为了求连续几项的和,必须先进行预处理(也就是前缀和)
每当输入第x个数,那么从第一个到加上现在这个数的和就等于输入前x-1个数加上第x个数
比如说有一组数据 1 3 2 9 6 那么当输入第五个数,求前五个数的和(1+3+2+9+6)就等于前四个数的和(1+3+2+9)加上第五个数(6)
下一步就是用代码实现这个过程
for(int i=1;i<=n;i++)
{
int k;
cin>>k;
f[i]=f[i-1]+k;
}
每次输入一个数k,进行预处理
差分
下一步就是计算a[i]+a[i+1]+a[i+2]+.......+a[j] (a数组为存储输入的数据的数组)
用到的原理——容斥原理
即a[1]+a[2]+......+a[j-1]+a[j]-(a[1]+a[2]+.....+a[i-2]+a[i-1])=a[i]+a[i+1]+a[i+2]+.......+a[j]
∵ a[1]+a[2]+......+a[j-1]+a[j]=f[j] 且a[1]+a[2]+.....+a[i-2]+a[i-1]=f[i-1]
∴a[1]+a[2]+......+a[j-1]+a[j]-(a[1]+a[2]+.....+a[i-2]+a[i-1])=f[j]-f[i-1]=a[i]+a[i+1]+a[i+2]+.......+a[j]
给出本题参考代码
#include<bits/stdc++.h>
using namespace std;
int T;
int j;
long long a[1000000];
long long b[1000000];
int main()
{
cin>>T;
for(int i=1;i<=T;i++)
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int k;
cin>>k;
a[i]=a[i-1]+k;
}
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
j++;
b[j]=a[y]-a[x-1];
}
}
for(int i=1;i<=j;i++)
{
cout<<b[i]<<endl;
}
return 0;
}
“谢谢教练”
又过了一段时间,小白已经不是小白了,正在准备CSP的复赛。
今天教练让全班做了模拟测试,小白做到第二题时,一眼就发现这是一道一位前缀和,他欣喜若狂,马上就将代码打了出来,并且最后拿了100。可惜第三题又把它给难住了
于是他决定用四重循环枚举(也是厉害)
然后
他又去问教练。
于是教练又传授了他一招——二维前缀和与差分!!!!!!
二维前缀和与差分
预处理
定义一个二维f数组存储从f[1][1]到输入的数所在坐标的前缀和(即矩形内所有元素的和)
同样是利用容斥原理
用a数组存储输入的数
就用上文题目的样例数据说明
假设现在输入[2,3]的数5,就要计算从[1,1]到[2,3]围成矩形的和(这是为了后面在循环枚举各个矩形和是能通过简单的加减算出答案,而不是每次进行循环枚举每在矩形里的数现场求和。这样能极大的节约时间)
即sum([2,3])=sum([2,2])+sum([1,3])-sum([1,1])
∵sum([2,2])=f[2][2] sum([1,3])=f[1][3] sum([1,1])=f[1][1]
∴sum([2,3])=f[2][3]=f[2][2]+f[1][3]-f[1][1]
推出 f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]
与处理完后,得到任意一点到a[1][1]围成矩形里所有数之和
差分
在本题中求得是面积一定下数之和最大
同样是容斥原理
只不过现在的面积不是一,二是一个待求的矩形;
相信聪明的你一定可以自己画图理解其中的道理,这里直接给出转移方程
假设矩形长r,宽q
则有 面积和=f[i][j]-f[i-r][j]-f[i][j-q]+f[i-r][j-q]
下面给出本题参考代码
#include<bits/stdc++.h>
using namespace std;
int n,m,r,c;
int t;
int f[1001][1001];
int a[1001][1001];
int main()
{
cin>>n>>m>>r>>c;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
int x;
cin>>Map[i][j];
f[i][j]=a[i][j]+f[i-1][j]+f[i][j-1]-f[i-1][j-1];
}
}
for(int i=1;i<=n-r+1;i++)
{
for(int j=1;j<=m-c+1;j++)
{
t=max(t,f[i+r-1][j+c-1]+f[i-1][j-1]-f[i-1][j+c-1]-f[i+r-1][j-1]);
}
}
cout<<t;
}
总结
1.一维前缀和预处理转移方程 f[i+1]=f[i]+输入的数
一维前缀差分转移方程 sum=f[j]-f[i-1]
2.二维前缀和预处理转移方程 f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]
二维前缀差分转移方程 面积和=f[i][j]-f[i-r][j]-f[i][j-q]+f[i-r][j-q]
3.差分的实质——容斥原理