1.一维前缀和
题意
给一个数组,求有多少区间的和是k的倍数
算法
枚举 O(n3)O(n3)
可以很简单的写出暴力代码,直接三重循环
for(int r = 1; r <= n; r ++)
for(int l = 1; l <= r; l ++)
{
int sum = 0;
for(int i = l; i <= r; i ++)
sum += a[i];
if(sum % k == 0) ans ++;
}
枚举左右区间端点l,r,求出区间和sum判断是否为k的倍数,然后记录在答案ans上。
可以看出第三重循环的作用就是算出区间[l,r]的和,所以用前缀和来优化这重循环。
前缀和 O(n2)O(n2)
预处理一下数组a[],将前缀和存入s[]中,每次查询就只需要O(1)O(1)的时间了
for(int i = 1; i <= n; i ++) s[i] = s[i - 1] + a[i]; // 求前缀和
for(int r = 1; r <= n; r ++)
for(int l = 1; l <= r; l ++)
if((s[r] - s[l - 1]) % k == 0) ans ++;
但是这个时间还是会炸,所以还需要优化一层。
数学 O(n)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n , k ; //n:数组长度 k:倍数
LL s[N] , cnt[N];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
s[i] += s[i-1];
}
LL res = 0;
cnt[0] = 1;
for(int i=1;i<=n;i++)
{
res += cnt[s[i]%k];
cnt[s[i]%k]++;
}
printf("%lld\n",res);
return 0 ;
}
代码分析
cnt[0] = 1;
前缀和取余%操作后等于0,其本身就可以作为一个K倍区间。
for(int i=1;i<=n;i++)
{
res += cnt[s[i]%k];
cnt[s[i]%k]++;
}
确定右边,查找左边相同的取余结果
2.二维前缀和
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e3+10;
int s[N][N];
int n,r; //n:目标个数 r:爆炸范围
int main()
{
cin>>n>>r; //读取目标个数n 和爆炸范围r
r=min(5001,r); // 爆炸范围超过5001时覆盖了所有目标范围,因此没必要考虑超出范围。
while(n--)
{
int x,y,w;
cin>>x>>y>>w; //读取目标位置x、y和价值w
s[++x][++y]+=w; // 将目标范围统一偏移1,便于边界计算。 //一个位置可能有多个目标 +=
}
for(int i=1;i<=5001;i++)
{
for(int j=1;j<=5001;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1]; //预处理
}
int res=0;
for(int i=r;i<=5001;i++)
{
for(int j=r;j<=5001;j++)
{
res=max(res,s[i][j]-s[i-r][j]-s[i][j-r]+s[i-r][j-r]); //枚举每种爆炸总价值,并找出最大总价值
}
}
cout<<res<<endl;
return 0;
}