【算法】最大子矩阵——悬线法

文章目录

      • 问题引入
      • 介绍
    • 代码实现
      • 定义
      • 初始化
      • 计算
      • Code
    • 总结
      • 其他例题

问题引入

国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。据说国际象棋起源于易经的思想,棋盘是一个8×8大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。而我们的主人公小Q,正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小”决定将棋盘扩大以适应他们的新规则。小Q找到了一张由N×M个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。小Q想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。不过小Q还没有决定是找一个正方形的棋盘还是一个矩形的棋盘(当然,不管那种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。于是小Q找到了即将参加全国信息学竞赛的你,你能帮助他么?
对于百分之20的数据 N,M<=80
对于百分之40的数据 N,M<=400
对于百分之100的数据 N,M<=2000

怎么做呢?暴力吗?不行,暴力的时间复杂度过高。如果只想拿部分分你就可以点击网页右上角的X了
今天给各位大佬介绍一下悬线法。

介绍

悬线法思路:悬线的定义,就是一条竖线,这条竖线要满足上端点在整个矩形上边界或者是一个障碍点。然后以这条悬线进行左右移动,直到移至障碍点或者是矩阵边界,进而确定这条悬线所在的极大矩阵。

代码实现

定义

我们定义 h [ i ] [ j ] h[i][j] h[i][j]表示点 ( i , j ) (i,j) (i,j)的悬线的长度。(及以点 ( i , j ) (i,j) (i,j),为边界的最大子矩阵的宽)
l [ i ] [ j ] l[i][j] l[i][j]表示点 ( i , j ) (i,j) (i,j)的悬线往左最长能延伸到哪。
r [ i ] [ j ] r[i][j] r[i][j]表示点 ( i , j ) (i,j) (i,j)的悬线往右最长能延伸到哪。
其中l和r数组存的是坐标。
如图:
【算法】最大子矩阵——悬线法_第1张图片
蓝色的为悬线,红色的为障碍物,那么绿色的线就是它的延伸范围,及l和r。
所以我们可以得出:
h [ 3 ] [ 3 ] = 2 , l [ 3 ] [ 3 ] = 2 , r [ 3 ] [ 3 ] = 4 h[3][3]=2,l[3][3]=2,r[3][3]=4 h[3][3]=2,l[3][3]=2,r[3][3]=4

初始化

由于我们的l和r数组记录的是坐标,所以我们可以初始化 l [ i ] [ j ] = r [ i ] [ j ] = j l[i][j]=r[i][j]=j l[i][j]=r[i][j]=j
而每个点都必定是个子矩阵,所以 h [ i ] [ j ] = 1 h[i][j]=1 h[i][j]=1


然后我们开始处理l和r数组
我们先把障碍物标记为0,空位标记为1,用a[i][j]存储。
则我们可以得出下组:

	for(int i=1;i<=n;i++)
		for(int j=2;j<=m;j++)
			if(a[i][j]+a[i][j-1]==2)
				l[i][j]=l[i][j-1];

	for(int i=1;i<=n;i++)
		for(int j=m-1;j>=1;j--)
			if(a[i][j]+a[i][j+1]==2)
				r[i][j]=r[i][j+1];

因为我们记录的是坐标,所以直接存起来就可以了,非常的easy

但是对于本题来说,他要的是01相间,所以应该是:

	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			std::cin>>a[i][j],h[i][j]=1,l[i][j]=r[i][j]=j;
	for(int i=1;i<=n;i++)
		for(int j=2;j<=m;j++)
			if(a[i][j]+a[i][j-1]==1)
				l[i][j]=l[i][j-1];
	for(int i=1;i<=n;i++)
		for(int j=m-1;j>=1;j--)
			if(a[i][j]+a[i][j+1]==1)
				r[i][j]=r[i][j+1];

计算

悬线每向下扩展一个点,就要对l和r进行修改,我们要取离 ( i , j ) (i,j) (i,j)近的坐标,同时还要保证悬线可以扩展,再加上符合题目的要求并记录ans。

	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(i>1&&a[i][j]+a[i-1][j]==1)
			{
				r[i][j]=min(r[i][j],r[i-1][j]);
				l[i][j]=max(l[i][j],l[i-1][j]);
				h[i][j]=h[i-1][j]+1;
			}
			ans=max(ans,min(r[i][j]-l[i][j]+1,h[i][j]));
			ans1=max(ans1,(r[i][j]-l[i][j]+1)*h[i][j]);
		}

Code

#include
#define int long long
const int N=1001;
int n,m,l[N][N],r[N][N],ans,a[N][N],h[N][N],u,x,y,ans1;
char op;
inline int max(int x,int y)
{
	return x>y?x:y;
}
inline int min(int x,int y)
{
	return x<y?x:y;
}
inline void init()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(NULL);
	std::cout.tie(NULL);
}
signed main()
{
	init();
	std::cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			std::cin>>a[i][j],h[i][j]=1,l[i][j]=r[i][j]=j;
	for(int i=1;i<=n;i++)
		for(int j=2;j<=m;j++)
			if(a[i][j]+a[i][j-1]==1)
				l[i][j]=l[i][j-1];
	for(int i=1;i<=n;i++)
		for(int j=m-1;j>=1;j--)
			if(a[i][j]+a[i][j+1]==1)
				r[i][j]=r[i][j+1];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(i>1&&a[i][j]+a[i-1][j]==1)
			{
				r[i][j]=min(r[i][j],r[i-1][j]);
				l[i][j]=max(l[i][j],l[i-1][j]);
				h[i][j]=h[i-1][j]+1;
			}
			ans=max(ans,min(r[i][j]-l[i][j]+1,h[i][j]));
			ans1=max(ans1,(r[i][j]-l[i][j]+1)*h[i][j]);
		}
	std::cout<<ans*ans<<"\n"<<ans1;
}

总结

悬线法确实好用,但时空复杂度均高达 O ( n m ) O(nm) O(nm),所以如果数据特别大的话还是用另一种方法。

其他例题

农夫约翰想要在他的正方形农场上建造一座正方形大牛棚。他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方。我们假定,他的农场划分成N×N的方格。输入数据中包括有树的方格的列表。你的任务是计算并输出,在他的农场中,不需要砍树却能够修建的最大正方形牛棚。牛棚的边必须和水平轴或者垂直轴平行。其中‘.’表示没有树的放歌,‘#’表示有树的方格

百分之20的数据保证 1<=N<=10,T=1
百分之百的数据保证 1<=N<=1000,1<=T<=10000

#include
const int N=1001;
int n,m,l[N][N],r[N][N],ans,a[N][N],h[N][N],u,x,y;
char op;
inline int max(int x,int y)
{
	return x>y?x:y;
}
inline int min(int x,int y)
{
	return x<y?x:y;
}
inline void init()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(NULL);
	std::cout.tie(NULL);
}
signed main()
{
	init();
	std::cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			a[i][j]=1,h[i][j]=1,l[i][j]=r[i][j]=j;
	int x,y;
	for(int i=1;i<=m;i++)
		std::cin>>x>>y,a[x][y]=0;
	for(int i=1;i<=n;i++)
		for(int j=2;j<=n;j++)
			if(a[i][j]+a[i][j-1]==2)
				l[i][j]=l[i][j-1];
	for(int i=1;i<=n;i++)
		for(int j=n-1;j>=1;j--)
			if(a[i][j]+a[i][j+1]==2)
				r[i][j]=r[i][j+1];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			if(i>1&&a[i][j]+a[i-1][j]==2)
			{
				r[i][j]=min(r[i][j],r[i-1][j]);
				l[i][j]=max(l[i][j],l[i-1][j]);
				h[i][j]=h[i-1][j]+1;
			}
			ans=max(ans,min(r[i][j]-l[i][j]+1,h[i][j]));
		}
	std::cout<<ans;
}

你可能感兴趣的:(C++算法,算法,矩阵,c++)