上一次比赛了解了一下前缀和算法,今天特地总结一下。
前缀和是一种预处理,让你在后面的计算中可以直接应用前面已经算出的结果。下面说几道例题,巩固一下。
一:
题目链接:子段求和
如果我们不用sum[i]保存前i项的和,那么每次查询都要遍历一遍,如果记录下来,那么后面的查询直接相减就可以了。
#include <string.h>
#include <stdio.h>
int main()
{
long long sum[50001],N,Q,A,B,i,a[50001];
scanf("%lld",&N);
sum[0]=0;
for(i=1;i<=N;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
scanf("%lld",&Q);
for(i=1;i<=Q;i++)
{
scanf("%lld %lld",&A,&B);
printf("%lld\n",sum[A+B-1]-sum[A-1]);
}
}
二:
题目链接:最大子矩阵
这是我们比赛中的一道题目,没有见过这种题型,所以上来就是各种循环遍历,果然超时了。
后来看师哥题解才知道原来这是经典的动态规划问题,利用二维前缀和计算。
- dp[i][j]代表左上角是矩阵第一个元素,右下角是a[i][j](就是代码中需要输入的dp[i][j])的矩形的元素总和.
- dp[i][j]-dp[i-x][j]-dp[i][j-y]+dp[i-x][j-y]代表以a[i][j]为矩阵右下角,大小为x*y的矩阵得元素总和。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
int dp[1010][1010];
int main()
{
int T,maxer,m,n,x,y,i,j;
cin>>T;
while(T--)
{
memset(dp,0,sizeof(dp));
maxer=-1;
cin>>m>>n>>x>>y;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
{
cin>>dp[i][j];
dp[i][j]+=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1];
if(i>=x&&j>=y)
maxer=max(maxer,dp[i][j]-dp[i-x][j]-dp[i][j-y]+dp[i-x][j-y]);
}
cout<<maxer<<endl;
}
}
三:
题目链接:Color the ball
每次输入两个端点,我们把左端点的值加一,右端点右边的点的值减一,因为一个数出现的次数=以这个数为左端点的次数+这个数不是左端点的次数。左端点的值+1表示这个数是左端点,右端点右边的点的值-1代表这一段区间被这个点截断,那么这个点就不加进来了。比如说有两段区间:1到3和4到6,我们已经计算出了点3的出现次数为1,我们算4出现的次数的时候,一次是以4为起点的那个,再加上3出现的次数(为什么要加上3出现的次数呢,可以这样认为,因为我们其实是假设相邻两个数是同时出现的,如果不同时出现就会被后面的数截断)也就是1,这就是2次了,但是因为第一段区间被4截断了,所以还有减掉一次,所以就是一次了。合计合计c[i]就代表了该数字的最终出现次数。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
int c[100001];
int main()
{
int N,A,B,i;
while(cin>>N && N)
{
memset(c,0,sizeof(c));
for(i=1;i<=N;i++)
{
cin>>A>>B;
c[A]++;
c[B+1]--;
}
for(i=1;i<=N;i++)
{
c[i]+=c[i-1];
printf("%d%c",c[i],i==N?'\n':' ');
}
}
}
好吧,就先列这几道题目把,以后继续补充。