前缀和
当我们想去快速求出来静态数组的某一个区间内所有数的和的时候,我们就可以用到前缀和的思想进行求解。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=100010;
int n,m;
int a[N];//原数组
int s[N];//前缀和数组 全局数组初值默认是0
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
s[i]=s[i-1]+a[i];
}
while(m--)//询问m次
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",s[r]-s[l-1]);
}
return 0;
}
①子矩阵的和
- 如何从原矩阵中得到前缀和矩阵(容斥原理)
S(xy)=S(x-1.y)+S(x.y-1)-S(x-1.y-1)+a(x.y)
文字表达:前缀和矩阵等于基准格的上面所有的格子之和加上基准格左边所有格子之和再减去被加了两次的重合部分最后加上基准格本身。
- 如何利用前缀和矩阵,计算某一个子矩阵的和?(容斥原理)
a(x.y)=S(xy)-S(x-1.y)-S(x.y-1)+S(x-1.y-1)
其实是同一个公式。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m,q;
int a[N][N],s[N][N];
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
while(q--)
{
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
printf("%d\n",s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);
}
return 0;
}
②激光炸弹
R最大值为5001
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5010;
int n,m;//长、宽
int s[N][N];//同时用于记录位置和前缀和数组(记录价值)
int main()
{
int cnt,R;//点的个数和边长
cin>>cnt>>R;
R=min(5001,R);//若越界则取可取得最大值
n=m=5001;
while(cnt--)
{
int x,y,w;
cin>>x>>y>>w;//传入坐标和价值
x++;y++;//防止数据越界
s[x][y]=s[x][y]+w;
}
//预处理前缀和
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
int res=0;
//枚举所有边长是R的矩形 枚举(i,j)为右下角坐标
for(int i=R;i<=n;i++)
for(int j=R;j<=m;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;
}
③k倍区间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hn5n330V-1649381659749)(C:/Users/21408/Desktop/image-20220405234457217.png)]
连续数列从1~n
考虑进行枚举 枚举右端点和左端点 将所有的子区间都枚举到 之后判断此区间是否为k倍区间(再进行一个暴力搜索)
求一下区间和 check一下是不是K的倍数(取模为0)
(肯定会超时Orz……)
那就浅优化一下吧。求和可以用前缀和!!
int res =0;
for(R=1;R<=n;R++)
for(L=1;L<=R;L++)
{
int sum=s[R]-s[L-1]
if(sum%k==0) res++;
}
还能怎么优化呢?
把问题
当R固定时,在1~R之间找到有多少个L满足(S[R]-S[L-1])%k==0
转换为:
当R固定时,在0~R-1之间找到有多少个L满足(S[R]-S[L])%k==0
即:有多少个S[L]与S[R]模k的余数相同(存余数)
int res=0;
for(R=1;R<=n;R++)
{
res+=cnt[R%k];
cnt[R%k]++;//余数是i的数有多少个
}
完整代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;//防止越界
const int N=100010;
int 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];//k倍区间的数量
cnt[s[i]%k]++;//余数是si%k的个数
}
printf("%lld\n",res);
return 0;
}
扩展调试技巧:
void f()
{
exit(0)
}
把f()放到程序中的各个位置可以去判断到底是哪里的Segmentation Fault错误