洛谷P3941入阵曲
题面:
题意:
给你一个的$n*m$矩阵,每个格子里都有一个不超过$k$的正整数。询问这个矩阵里有多少个不同的子矩形中的数字之和是$k$的倍数?
题解:
我们先考虑一个简化版的一维问题:给定一个长度为$n$的序列,$a[1],a[2],\cdots,a[n]$,如果某一段子序列的和为$k$的倍数,则称其为$k$倍区间,求该序列中有多少个$k$倍区间,要求时间复杂度为$O(n)$。
设$sum$为该序列的前缀和,那么若$(sum[r]-sum[l-1])%k==0$,则区间$[l,r]$符合条件,但暴力枚举复杂度为$O(n^2)$,不符合条件。
考虑优化,将上式变形后为$sum[l-1]%k==sum[r]%k$,所以我们可以用桶来处理在$i$之前前缀和为$sum[i]$的数量。
继续考虑原问题,我们可以枚举两行,再枚举列,把这一列合为一个数,即可用上述方法解决。
code:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; #define R register #define ll long long inline ll read(){ ll aa=0;R int bb=1;char cc=getchar(); while(cc<'0'||cc>'9') {if(cc=='-')bb=-1;cc=getchar();} while(cc>='0'&&cc<='9') {aa=(aa<<1)+(aa<<3)+(cc^48);cc=getchar();} return aa*bb; } const int N=403; const int M=1e6+3; int n,m,mod,sum[N][N],a[N]; ll ans,cnt[M]; int main() { n=read();m=read();mod=read(); for(R int i=1,num;i<=n;++i){ num=0; for(R int j=1,x;j<=m;++j){ x=read(); num+=x; if(num>=mod)num-=mod; sum[i][j]=num+sum[i-1][j]; if(sum[i][j]>=mod)sum[i][j]-=mod; } } for(R int i=1;i<=n;++i){ for(R int j=i;j<=n;++j){ //cnt[0]=1; for(R int k=1;k<=m;++k){ a[k]=(sum[j][k]-sum[i-1][k]+mod)%mod; ans+=cnt[a[k]]; ++cnt[a[k]]; } ans+=cnt[0]; //可理解为加上本来就为k的倍数的区间个数,可写成上述注释。 for(R int k=1;k<=m;++k)cnt[a[k]]=0; } } printf("%lld\n",ans); return 0; }