[JZOJ5714]矩阵

题目大意

求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>=kcb
当c’比较小时,若导致 kcb>a ,贡献为0。
当c’比较大时,若导致 kcb<1 ,贡献为a*(c-c’+1)。
通过二分或预处理得到这两个分界点,然后统计即可。
在正常区间内,会贡献 (a+bkc+1)(cc+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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值