01矩阵前缀和

题目描述

给定一个 \(n*m\)的 01 矩阵, 求包含 \([l,r]\)个 1的子矩形个数。

输入格式

第一行,两个正整数 n,m。 接下来 n行每一个长度为 m的 01 串,表示给定的矩阵。 接下来 一行, 两个自然数 l,r。

Solution

首先暴力枚举显然不可取

注意到,n非常小,可以考虑一种\(O(n^2m)\)的方法

很套路,要将询问拆成\([0,L-1]\)\([0,R]\)来处理

\(O(n^2)\)来枚举矩形的上边和下边,

而前缀和又是单调的,用个指针维护就好了,

具体的:如果我们已经确定了\(i\)列到\(j\)列间的1的个数在\([l,r]\)中,k列到j列的1也在\([l,r]\)中,并且满足第\(i\)列比第\(k\)列小,那么,矩形的一边为\(j\)的情况下,另一边可以为第\(i\)列到第\(k\)列间的任意一列。
所以,我们就可以通过寻找边界的情况快速找到矩形的右边固定情况所产生的答案。然后将矩形的右边向右移动,判断矩形的左边的范围是否变动,然后将这个范围继续加入答案,就可以解出答案了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define re register int
using namespace std;
char s[50010];
long long n,m,i,j,p[50010],sum[40][50010],q[50010],ans,k,l,r;
int main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		for(int j=1;j<=m;j++)
			sum[i][j]=sum[i-1][j]+s[j]-'0';
	}
	scanf("%lld%lld",&l,&r);
	for(int i=1;i<=n;i++)//上
		for(int j=i;j<=n;j++){//下
			long long x=0,y=0;
			for(int k=1;k<=m;k++){
				p[k]=sum[j][k]-sum[i-1][k];
				q[k]=q[k-1]+p[k];
			}
			for(int k=1;k<=m;k++){
				while(q[k]-q[x]>=l&&x<k) x++;
				while(q[k]-q[y]>r&&y<k) y++;
				ans+=x-y;
			}
		}
	printf("%lld\n",ans);
	return 0;
} 
posted @ 2020-11-10 16:51  ke_xin  阅读(75)  评论(0编辑  收藏  举报