01 矩阵切割拼凑最大全 0 矩形:题解解析及代码详解

题目大意

将一个 nm 的 01 矩阵沿着列切几刀,拆成若干个 nw_i 大小的矩阵,你可以重新任意排列它们来拼成一个新的 n*m 的 01 矩阵,使得矩阵中最大的全 0 矩形面积最大。

Solution

这题关键在于 n*m ≤ 10^5,即矩阵大小不超过 10^5。一种直觉是平衡规划,即分 n ≤ √10^5 和 m ≤ √10^5 两种情况分别设计算法。

n ≤ √10^5

枚举最终全 0 矩形的上下边界 l, r,然后对每个小矩阵分类。若小矩阵内第 l 行至第 r 行全部为 0,分为 I 类,否则分为 II 类。

最后肯定是把 I 类矩阵放在中间,两边放 II 类矩阵来凑成一个全 0 矩形,那么,我们需要计算 I 类矩阵宽度之和,以及 II 类矩阵在左边最多几列为全 0,右边最多几列为全 0。需要注意的是,左右不能用 II 类矩阵中的同一个,所以我们还要记录一下次大值,不能用最大值时用次大值替换。

时间复杂度 O(n^2m)=O(10^5n),n 是根号级别的,显然不会超时。

m ≤ 10^5

这时就不能直接枚举矩形的上下边界了,我们可以用另一种方法枚举上下边界:

记录矩形中每个点 (i,j) 往上第一个 1 的行号(记作 up[i][j])。枚举矩形中的每个点 (i,j),将 up[i][j] 作为上边界,i 作为下边界。(替换了原来暴力枚举的上边界 r,下边界 l)

剩下的过程套用上面的即可,时间复杂度 O(nm^2)=O(10^5m),m 是根号级别的,也不会超时。

至此,问题解决。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=100007;

int T;
int s,n,m,ans,w[N],st[N],en[N],sum[N],a[N],up[N];

int id(int x,int y){
	if(x<1||y<1)return 0;
	return (y-1)*n+x;
}
int get(int x1,int x2,int y1,int y2){
	return sum[id(x2,y2)]-sum[id(x1-1,y2)]-sum[id(x2,y1-1)]+sum[id(x1-1,y1-1)];
}

void calc(int l,int r){
	for(int i=1;i<=s;++i)for(int j=st[i],ret=0;j<=en[i];++j){
		if(get(l,r,j,j)==0)++ret;
		else ret=0;
		ans=max(ans,(r-l+1)*ret);
	}
	int L[2][2],R[2][2],mid=0;
	memset(L,0,sizeof(L));
	memset(R,0,sizeof(R));
	for(int i=1;i<=s;++i){
		int mxl=0,mxr=0;
		for(int j=st[i];j<=en[i];++j)if(get(l,r,j,j)==0)++mxl;else break;
		for(int j=en[i];j>=st[i];--j)if(get(l,r,j,j)==0)++mxr;else break;
		if(mxl==w[i])mid+=w[i];
		else{
			if(mxl>L[0][0])L[0][0]=mxl,L[0][1]=i;
			else if(mxl>L[1][0])L[1][0]=mxl,L[1][1]=i;
			if(mxr>R[0][0])R[0][0]=mxr,R[0][1]=i;
			else if(mxr>R[1][0])R[1][0]=mxr,R[1][1]=i;
		}
	}
	for(int p=0;p<2;++p)for(int q=0;q<2;++q)if(L[p][1]!=R[q][1])ans=max(ans,(r-l+1)*(mid+L[p][0]+R[q][0]));
	ans=max(ans,(r-l+1)*(mid+L[0][0]));
	ans=max(ans,(r-l+1)*(mid+R[0][0]));
}

int main(){
	scanf('%d',&T);
	while(T--){
		m=ans=0;
		scanf('%d%d',&s,&n);
		memset(w,0,sizeof(w));
		memset(st,0,sizeof(st));
		memset(en,0,sizeof(en));
		memset(sum,0,sizeof(sum));
		memset(a,0,sizeof(a));
		memset(up,0,sizeof(up));
		for(int i=1;i<=s;++i){
			scanf('%d',&w[i]);
			st[i]=m+1;
			for(int j=1;j<=n;++j)for(int k=1;k<=w[i];++k){
				char c;scanf(' %c',&c);
				a[id(j,m+k)]=c-'0';
			}
			m+=w[i],en[i]=m;
		}
		for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)sum[id(i,j)]=sum[id(i-1,j)]+sum[id(i,j-1)]-sum[id(i-1,j-1)]+a[id(i,j)];
		for(int j=1;j<=m;++j)
			for(int i=1,lst=0;i<=n+1;++i)
				if(i>n||a[id(i,j)])
					for(int k=lst+1;k<=i-1;++k)up[id(k,j)]=i-1;
				lst=i;
		}
		if(n<m)for(int i=1;i<=n;++i)for(int j=i;j<=n;++j)calc(i,j);
		else for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)calc(i,up[id(i,j)]);
		printf('%d\n',ans);
	}
	return 0;
}

总结

这道题是一道典型的思维题,需要通过观察数据范围和题目条件,找到合适的算法和数据结构来解决问题。代码中使用了前缀和、二分查找、枚举等技巧,体现了算法设计和优化的一般思路。希望这篇文章能够帮助你更好地理解这道题,并从中学习到一些算法技巧。

这篇题解是人写的,不是 AI 写的。

01 矩阵切割拼凑最大全 0 矩形:题解解析及代码详解

原文地址: https://www.cveoy.top/t/topic/ndNk 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录