最大子矩阵

概述

有一个矩阵 aa,有 kk 个障碍点。你要选取一个面积最大的矩阵,使得满足某些条件。可能的条件有:

  • 矩阵不能包含障碍点
  • 矩阵可以在外面一圈包含障碍点。

本文共介绍了两种解决该问题的方法,根据情况分别使用。

概念:

  • 最大子矩阵:最大有效子矩形为所有有效子矩形中最大的一个(或多个)。
  • 有效子矩阵:有效子矩形为内部不包含任何障碍点且边界与坐标轴平行的子矩形。
  • 极大子矩阵:有效子矩形,如果不存在包含它且比它大的有效子矩形,就称为极大子矩形。

第一种

(以下默认不能包含障碍点)

  • 数据范围为 1n,m1031\le n,m\le 10^3(矩阵大小)。
  • 时空复杂度:Θ(nm)\Theta(nm)
  • kk 大小无关。

以下称 xx 负半轴为左,正为右。yy 正为上,负为下。

对于一个矩阵,我们可以对于每个点分别考虑。考虑包含这个点矩阵的最左,最右,最上,最下。但如果对每个点分别处理。复杂度非常高。

这时候我们就要考虑预处理。

  • li,jl_{i,j} 为包含点 (i,j)(i,j)ii 上方障碍点(没有即边界)的极大子矩阵最左在 li,jl_{i,j}
  • ri,jr_{i,j} 为包含点 (i,j)(i,j)ii 上方障碍点(没有即边界)的极大子矩阵最右在 ri,jr_{i,j}
  • upi,jup_{i,j} 为包含点 (i,j)(i,j)ii 上方障碍点(没有即边界)的极大子矩阵最上在 upi,jup_{i,j}

先对每一行进行分类讨论,以下使用一些简单的递推知识。
在最开始时,可确定的是 ai,ja_{i,j} 的最左,右值都至少为 jj

  • ai,j,ai,j1a_{i,j},a_{i,j-1} 均合法,说明 li,jl_{i,j} 也可以继承 li,j1l_{i,j-1} 的最左值。
  • ai,j,ai,j+1a_{i,j},a_{i,j+1} 均合法,说明 ri,jr_{i,j} 也可以继承 ri,j+1r_{i,j+1} 的最右值。

注意

此时的 l,rl,r 只是一行中的最左,最右。


此时我们就可以边处理 upup,边更新 l,rl,r。然后统计答案了。
对于 upi,jup_{i,j},最初可以确定的值是 11。即本身。
(一种实现方法:upi,j1up_{i,j}\gets 1。当 (i,j)(i,j) 不合法时,不统计答案。)

因为对于点 (i,j)(i,j),要考虑上方情况,所以 l,rl,r 会受到上一行的影响。(只能继承上一行,否则不符合包含点 (i,j)(i,j)ii 上方障碍点,没有即边界的极大子矩阵的状态)

对于 li,j,li1,jl_{i,j},l_{i-1,j},两者之间只能取最大值。(取最小值会包含障碍)
同理可得 rr 的状态转移。

对于 upup 的状态转移:

  • 如果 ai,j,ai1,ja_{i,j},a_{i-1,j} 均合法,那么优解为继承 upi1,jup_{i-1,j}
  • 如果 ai,ja_{i,j} 合法,但 upi1,jup_{i-1,j} 不合法。只能为 11。(当前行)
  • 如果 ai,ja_{i,j} 不合法。那不存在包含该点的子矩阵。(不能统计答案)

以下为代码实现:

适用于:玉蟾宫

#include<bits/stdc++.h>
using namespace std;
const int N =1e3+10;
char a[N][N];
int n,m;
int l[N][N],r[N][N],up[N][N],ans=0; 
int main() {
	cin>>n>>m;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			cin>>a[i][j];
			l[i][j]=r[i][j]=j;up[i][j]=1;
		}
	}
	for(int i=1;i<=n;i++) {
		for(int j=2;j<=m;j++) if(a[i][j-1]=='F'&&a[i][j]=='F') l[i][j]=l[i][j-1];
		for(int j=m-1;j>=1;j--) if(a[i][j]=='F'&&a[i][j+1]=='F') r[i][j]=r[i][j+1];
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(a[i][j]=='F') {
				if(a[i-1][j]=='F')
					up[i][j]=up[i-1][j]+1,
					l[i][j]=max(l[i][j],l[i-1][j]),
					r[i][j]=min(r[i][j],r[i-1][j]);
				ans=max(ans,up[i][j]*(r[i][j]-l[i][j]+1));
			}
		}
	}
	cout<<ans*3;
	return 0;
}
/*
5 5
R R R R R 
R R R R R 
R R R R R 
R R R R R 
R R R R R 
*/

第二种

(以下默认为边界可以为障碍点)

第一种方法的时空复杂度:Θ(nm)\Theta(nm)。当 nmnm 很大,但 kk 很小的时候,效率低下,空间耗费大。以下将介绍一种时间复杂度为 Θ(k2)\Theta(k^2) 的方法。

  • 空间复杂度 Θ(k)\Theta(k)
  • 时间复杂度 Θ(k2)\Theta(k^2)

根据定义可知,极大子矩阵的的四条边都要在障碍点或边界上,否则继续拓展仍能获得更大的子矩阵。

我们可以对所有障碍点对行,列分别排序。以每个障碍点 ii 为起点,求包含该障碍点的极大子矩阵。

我们可以将极大子矩阵的终点设为其他障碍点,使效率更高。
以下对列排好序的情况讨论。

因为已经被排序,所以上,下界取决于每个障碍点。我们要做的事情就是维护上,下界。以下设上界到下界 0n0\sim n

  • 对于上界:upmaxyjyiyjup\gets\max\limits_{y_j\le y_i}{y_j}

  • 对于下界:downminyj>yiyjdown\gets\min\limits_{y_j > y_i}{y_j}


我们会发现,现在的写法无法处理 开始为左边界,结束为右边界。(上下同理)。

此时我们要在矩阵的四个端点上加入障碍点。


#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+10;
#define x(i) x[mp[i]]
#define y(i) y[mp[i]]
int n,m,s,ans;
int x[N],y[N],mp[N],up,down;
int main() {
	cin>>n>>m>>s;
	for(int i=1;i<=s;i++) {
		cin>>x[i]>>y[i];mp[i]=i;
	}
	x[s+1]=0,y[s+1]=0,mp[s+1]=s+1;
	x[s+2]=0,y[s+2]=m,mp[s+2]=s+2;
	x[s+3]=n,y[s+3]=0,mp[s+3]=s+3;
	x[s+4]=n,y[s+4]=m,mp[s+4]=s+4;
	s=s+4;
	sort(mp+1,mp+s+1,[](int a,int b) {return x[a]<x[b];});
	for(int i=1;i<=s;i++) {
		up=0,down=m;
		for(int j=i+1;j<=s;j++) {
			ans=max(ans,(down-up)*(x(j)-x(i)));
			if(y(j)>=y(i)) down=min(down,y(j));
			else up=max(up,y(j));
		}
	}
	sort(mp+1,mp+s+1,[](int a,int b) {return y[a]<y[b];});
	for(int i=1;i<=s;i++) {
		up=0,down=n;
		for(int j=i+1;j<=s;j++) {
			ans=max(ans,(down-up)*(y(j)-y(i)));
			if(x(j)>=x(i)) down=min(down,x(j));
			else up=max(up,x(j));
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2023-08-17 20:18  cjrqwq  阅读(42)  评论(0)    收藏  举报  来源