题目大意
一堆桌子被排列成n×m(范围为[1,500])的矩形,a[i][j]=1表示位置(i,j)处有桌子,0表示没有。我们要寻找满足这些条件的子矩形:
- 该子矩形的四条边上没有空位;
- 子矩形中的空位与桌子的数量之差不超过1(不包括侧面的桌子);
- 子矩形的长度和宽度必须大于1。
有多少个子矩形可以满足要求?
解题思路
500×500的矩阵,我们最多可以跑的算法。那么对付这样的枚举子矩阵题,我们的惯用套路就是用前缀和来维护了。我们在原矩阵中把0则当做-1,求出要求的0和1的差,记录前缀和。注意:前缀和不能算上边界的1。
一种种查找的方法就是枚举上下行边界,再在每列查找。每个子矩阵要求四条边上都是1,所以我们枚举上下边界后,找一段在这两行都是1的列区间,再在这个区间里找。
枚举每一列,如果这一列也都是1,就可以统计进去。那么每次找到合法的列,查询前面的前缀和是否有和它相差1以内的,计入答案。然后把自己的前缀和加入统计。
AC代码
#include<bits/stdc++.h> using namespace std; const int N=25e4+10,M=510; int a[M][M],b[M][M],f[N*2],s[M],n,m; long long ans; int main() { int x,i,j,k,l; s[0]=N; scanf("%d%d",&n,&m); for(i=1;i<=n;i++) for(j=1;j<=m;j++) { scanf("%d",&a[i][j]); if(!a[i][j]) a[i][j]=-1; b[i][j]=b[i-1][j]+a[i][j]; } for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) { x=1; for(k=1;k<=m;k++) { if(a[i][k]!=1 || a[j][k]!=1) { for(l=x;l<=k;l++) if(b[j][l]-b[i-1][l]==j-i+1) f[s[l]]--; x=k+1,s[k]=N; } else { if(b[j][k]-b[i-1][k]==j-i+1) ans+=f[s[k-1]]+f[s[k-1]+1]+f[s[k-1]-1]; s[k]=s[k-1]+b[j-1][k]-b[i][k]; if(b[j][k]-b[i-1][k]==j-i+1) f[s[k]]++; } } for(k=x;k<=m;k++) if(b[j][k]-b[i-1][k]==j-i+1) f[s[k]]--; } printf("%lld\n",ans); }