Codeforces 364E Empty Rectangles 【分治】

本文解析了CodeForces竞赛中一道题目E的解决方案,该题要求找出一个n*m的01矩阵中所有子矩阵的和等于K的数量。通过使用前缀和优化查询效率,并采用水平和竖直方向交替划分的方法,实现O(k)预处理,最终得出答案。

题目链接:https://codeforces.com/contest/364/problem/E

题意:给一个n*m的01矩阵,问有多少个子矩阵满足其和为K

题解:
考虑solve(x1,y1,x2,y2)solve(x1,y1,x2,y2)solve(x1,y1,x2,y2)表示[x1,y1]到[x2,y2]的答案,显然最后的答案就是solve(1,1,n,m)solve(1,1,n,m)solve(1,1,n,m)
套个前缀和就变成了O(1)O(1)O(1)查询,和为k可以看成面积为k
考虑按照水平划分中线(竖直同理),枚举左右边界,p1[k]p1[k]p1[k]表示中线向上和为k最多能延伸到哪,p2[k]p2[k]p2[k]同理,只不过表示向下,这个显然可以O(k)O(k)O(k)预处理,然后考虑和为k满足什么条件
(p1[k−1]−p1[k])∗(p2[K−k]∗p2[K−k−1])(p1[k-1]-p1[k])*(p2[K-k]*p2[K-k-1])(p1[k1]p1[k])(p2[Kk]p2[Kk1])
因为限定了左右边界,所以只需要考虑该面积的有多少个
p1[k−1]−p1[k]p1[k-1]-p1[k]p1[k1]p1[k]表示面积为kkk的限定了边界的矩形有多少个(因为可能上面有好几排0)
p2同理,k+(K−k)=Kk+(K-k)=Kk+(Kk)=K,所以这样恰好面积就是K了,注意边界
最后为了保证复杂度,需要水平竖直间隔着切

代码:

// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() puts("okkkkkkkk")
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)

using namespace std;

typedef long long LL;

const int inf = 1e9;

int n,m,K;
LL ans=0;
char s[2505][2505];
int sum[2505][2505],p1[20005],p2[20005];

int calc(int x1,int y1,int x2,int y2){
	return sum[x2][y2] - sum[x2][y1-1] - sum[x1-1][y2] + sum[x1-1][y1-1];
}

void solve(int x1,int y1,int x2,int y2,int horver){
//	printf("%d %d %d %d\n",x1,y1,x2,y2);getchar(); 
	if(x1>x2 || y1>y2)return ;
	if(x1 == x2 && y1 == y2){
		ans += (calc(x1,y1,x2,y2) == K);
		return ;
	}
	if(horver == 1){
		int mid=x1+x2>>1;
		solve(x1,y1,mid,y2,horver ^ 1);
		solve(mid+1,y1,x2,y2,horver ^ 1);
		for(int i=y1;i<=y2;i++){
			for(int k=0;k<=K;k++){
				p1[k]=mid, p2[k]=mid+1;
			}
			for(int j=y2;j>=i;j--){
				for(int k=0;k<=K;k++){
					while(p1[k] >= x1 && calc(p1[k],i,mid,j) <= k)-- p1[k];
					while(p2[k] <= x2 && calc(mid+1,i,p2[k],j) <= k)++ p2[k];
				}
				for(int k=1;k<K;k++){
					ans += (p1[k-1]-p1[k]) * (p2[K-k]-p2[K-k-1]);
				}
				if(K > 0){
					ans += (mid-p1[0]) * (p2[K]-p2[K-1]);
					ans += (p2[0]-mid-1) * (p1[K-1]-p1[K]);
				}else if(K == 0){
					ans += (mid-p1[0]) * (p2[0] - mid - 1);
				}
			}
		}
	}else{
		int mid=y1+y2>>1;
		solve(x1,y1,x2,mid,horver ^ 1);
		solve(x1,mid+1,x2,y2,horver ^ 1);
		for(int i=x1;i<=x2;i++){
			for(int k=0;k<=K;k++){
				p1[k]=mid, p2[k]=mid+1;
			}
			for(int j=x2;j>=i;j--){
				for(int k=0;k<=K;k++){
					while(p1[k] >= y1 && calc(i,p1[k],j,mid) <= k)-- p1[k];
					while(p2[k] <= y2 && calc(i,mid+1,j,p2[k]) <= k)++ p2[k];
				}
				for(int k=1;k<K;k++){
					ans += (p1[k-1]-p1[k]) * (p2[K-k]-p2[K-k-1]);
				}
				if(K > 0){
					ans += (mid-p1[0]) * (p2[K]-p2[K-1]);
					ans += (p2[0]-mid-1) * (p1[K-1]-p1[K]);
				}else if(K == 0){
					ans += (mid-p1[0]) * (p2[0] - mid - 1);
				}
			}
		}
	}
}

int main(){
	scanf("%d%d%d",&n,&m,&K);
	for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1] + (s[i][j] == '1');
	solve(1,1,n,m,0);
	printf("%I64d\n",ans);

	return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值