题目大意
求01网格图多少面积>=k的矩阵全0。
做法
预处理每个点往上延伸的长度up[i,j]。
对于每一行,我们顺序扫并维护单调栈。
弹出元素时考虑贡献。
假如弹出第k列,做到第l列,栈中上一个位置在第j列。
则得到一个高为up[i,k],长为l-j-1的矩形。
在这个矩形里求面积>=k的全0子矩阵个数(下边界必须是i)。
为了不计重,这个子矩阵的高要>max(up[i,j],up[i,l])。
考虑解决这样一个问题。
长为c,高要>b,最高是a+b。
假如选了一个(a’+b)*c’的,其中1<=a’<=a,1<=c’<=c。
那么
(a′+b)∗c′>=k
a′>=⌈kc′⌉−b
当c’比较小时,若导致
⌈kc′⌉−b>a
,贡献为0。
当c’比较大时,若导致
⌈kc′⌉−b<1
,贡献为a*(c-c’+1)。
通过二分或预处理得到这两个分界点,然后统计即可。
在正常区间内,会贡献
(a+b−⌈kc′⌉+1)∗(c−c′+1)
只要预处理一些前缀和即可统计。
复杂度O(nm)。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=4000+10;
int up[maxn][maxn],a[maxn][maxn],ce[maxn];
ll sum[maxn],num[maxn];
int sta[maxn];
int i,j,k,l,t,n,m,tot,top;
ll ans;
void solve(int a,int b,int c){
int l,r,mid,lc,rc;
if (ce[1]-b>a){
l=1;r=c;
while (l<r){
mid=(l+r+1)/2;
if (ce[mid]-b>a) l=mid;else r=mid-1;
}
lc=l+1;
}
else lc=1;
if (ce[c]-b<1){
l=lc;r=c;
while (l<r){
mid=(l+r)/2;
if (ce[mid]-b<1) r=mid;else l=mid+1;
}
rc=l-1;
ans+=(ll)a*(c+1)*(c-l+1);
ans-=(ll)a*(l+c)*(c-l+1)/2;
}
else rc=c;
ans+=(ll)(a+b+1)*(c+1)*(rc-lc+1);
ans-=(ll)(c+1)*(num[rc]-num[lc-1]);
ans-=(ll)(a+b+1)*(lc+rc)*(rc-lc+1)/2;
ans+=(ll)(sum[rc]-sum[lc-1]);
}
int main(){
freopen("matrix.in","r",stdin);freopen("matrix.out","w",stdout);
scanf("%d%d%d",&n,&m,&k);
fo(i,1,n){
if (k%i==0) t=k/i;else t=k/i+1;
num[i]=ce[i]=t;
sum[i]=(ll)t*i;
num[i]+=num[i-1];
sum[i]+=sum[i-1];
}
fo(i,1,n)
fo(j,1,m)
scanf("%d",&a[i][j]);
fo(i,1,n)
fo(j,1,m)
if (a[i][j]) up[i][j]=0;else up[i][j]=up[i-1][j]+1;
fo(i,1,n){
top=0;
fo(j,1,m+1){
while (top&&up[i][j]<=up[i][sta[top]]){
t=max(up[i][sta[top-1]],up[i][j]);
solve(up[i][sta[top]]-t,t,j-sta[top-1]-1);
top--;
}
sta[++top]=j;
}
}
printf("%lld\n",ans);
}