IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【最大子矩阵】扫描法和悬线法 -> 正文阅读

[数据结构与算法]【最大子矩阵】扫描法和悬线法

  • 博客主页: https://blog.csdn.net/qq_50285142
  • 欢迎点赞👍收藏?关注?留言 📝 如有错误,敬请指正
  • 🎈虽然生活很难,但我们也要一直走下去🎈

最大子矩阵问题

在一个给定的 n ? m n*m n?m矩形网格中有 n n n个障碍点,要找出网格内部不包含任何障碍点,且边界与坐标轴平行的最大子矩形。

扫描法

例题:奶牛浴场

题意:
n ? m n*m n?m的牧场里有一些产奶点,先要建造一个矩形的浴场,里面不能包含产奶点,产奶点可以在浴场边缘,求建造的最大面积

性质:

1.极大有效子矩形的四条边无法向四边拓展, 也就是说四条边都存在障碍点或与大矩形边界重合
2.存在一个障碍点的矩形中最大有效子矩形一定是极大有效子矩形

算法复杂度: O ( n 2 ) O(n^2) O(n2)

我们先考虑从上到下扫描点:
我们要维护一个左右边界l,r,保证左右边界之间没有障碍点存在
如果访问到的点j的纵坐标大于当前点i的纵坐标,更新右边界,取最小值
如果访问到的点j的纵坐标小于当前点i的纵坐标,更新左边界,取最大值

更新结果:
r e s = m a x ( r e s , ( r ? l ) ? ( p [ j ] . f i ? p [ i ] . f i ) ) res = max(res,(r-l)*(p[j].fi-p[i].fi)) res=max(res,(r?l)?(p[j].fi?p[i].fi))为左右边界之间的差乘上访问点j和当前点ix坐标之差,即为更新了 以访问点j和当前点i为上下的边界,以维护的左右l,r为左右边界所组成矩形的面积

然后从左到右扫描所有的点和上面时类似的道理

参考博客

#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef pair<int,int> pii;
const int N = 5005;

pii p[N];
int res;
int n,m,k;

int main()
{
	cin>>n>>m>>k;
	for(int i=1;i<=k;i++)
		cin>>p[i].fi>>p[i].se;
	//加入矩形的四个点
	p[++k] = {0,0};
	p[++k] = {0,m};
	p[++k] = {n,0};
	p[++k] = {n,m};
	
	sort(p+1,p+1+k,[](pii a,pii b){
		return a.fi<b.fi;
	});//按x坐标排序
	int l,r,v;//左右边缘
	for(int i=1;i<=k;i++)
	{
		l = 0,r = m,v = n-p[i].fi;
		for(int j=i+1;j<=k;j++)
		{
			if(v * (r-l) < res) break;//剪枝
			res = max(res,(r-l)*(p[j].fi-p[i].fi));
			if(p[j].se >= p[i].se) r = min(r,p[j].se);
			else l = max(l,p[j].se);
		}
	}
	
	sort(p+1,p+1+k,[](pii a,pii b){
		return a.se < b.se;
	});//按y坐标排序
	
	int u,d;//上下边缘
	for(int i=1;i<=k;i++)
	{
		u = 0,d = n,v = m-p[i].se;
		for(int j=i+1;j<=k;j++)
		{
			if(v * (d-u) < res) break;
			res = max(res,(d-u)*(p[j].se-p[i].se));
			if(p[j].fi >= p[i].fi) d = min(d,p[j].fi);
			else u = max(u,p[j].fi);
		}
	}
	cout<<res<<'\n';
	return 0;
 }

悬线法

例题:最大0矩阵

悬线:该点到上端的最长连续0长度

H [ i , j ] H[i,j] H[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)对应的悬线向右最多能够移动到的位置。
 
我们要维护每一格对应向上的最大连续0的长度,就是所谓的悬线,还要维护该条悬线向左向右扩展的最远位置的下标,即为上面标明的三个值。

结果计算就是每一格的对应面积 ( r [ i ] [ j ] ? l [ i ] [ j ] + 1 ) ? h [ i ] [ j ] (r[i][j]-l[i][j]+1)*h[i][j] (r[i][j]?l[i][j]+1)?h[i][j],所有取一个最大值。

1.维护 h [ i ] [ j ] h[i][j] h[i][j]:

  1. 当前位为0时: h [ i ] [ j ] = h [ i ? 1 ] [ j ] + 1 h[i][j] = h[i-1][j]+1 h[i][j]=h[i?1][j]+1,上面的一位的长度加一
  2. 当前位为1时: h [ i ] [ j ] = 0 h[i][j]=0 h[i][j]=0,因为初始化就为0,就没有操作

2.维护 l [ i ] [ j ] l[i][j] l[i][j]

需要额外声明一个 l l ll ll变量表示该行左侧 0最远到达的下标 l l ll ll,因为访问该行时,遇见1然后 l l ll ll就会更新为1的右面 j + 1 j+1 j+1的位置,碰见0就和上一行悬线左侧最远到达的位置进行最大更新

  1. 当前位为0时: l [ i ] [ j ] = m a x ( l [ i ? 1 ] [ j ] , l l ) l[i][j] = max(l[i-1][j],ll) l[i][j]=max(l[i?1][j],ll),使悬线位置最远,即x坐标最大
  2. 当前位为1时: l [ i ] [ j ] = 1 l[i][j]=1 l[i][j]=1,更新 l l ll ll j + 1 j+1 j+1

3.维护 r [ i ] [ j ] r[i][j] r[i][j]

需要从右边维护,道理和上面的相似

  1. 当前位为0时: r [ i ] [ j ] = m i n ( r [ i ? 1 ] [ j ] , r r ) r[i][j] = min(r[i-1][j],rr) r[i][j]=min(r[i?1][j],rr)
  2. 当前位为1时: r [ i ] [ j ] = n r[i][j]=n r[i][j]=n。n为初始值,保证递推的时候在最右端,如果访问到下面为0的位置的话, r [ i ? 1 ] [ j ] r[i-1][j] r[i?1][j]代表的就是为1的位,保证 r [ i ] [ j ] = m i n ( r [ i ? 1 ] [ j ] , r r ) r[i][j] = min(r[i-1][j],rr) r[i][j]=min(r[i?1][j],rr)中的 r [ i ? 1 ] [ j ] r[i-1][j] r[i?1][j]总是最大的,然后就可以取rr这个值(本行的到达最右端的位置)

维护完就可以计算结果了

另外本题需要使用滚动数组才能过掉,先理解二维,然后压缩成一维就可以了

MLE代码:(重在理解递推)

#include<bits/stdc++.h>
using namespace std;
const int N = 2005;

int a[N][N];
int res,n;
int h[N][N],l[N][N],r[N][N];

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin>>a[i][j];
	
	for(int i=1;i<=n;i++)
	{
		int ll=1,rr=n;
		for(int j=1;j<=n;j++)
		{
			if(a[i][j]) ll = j+1,l[i][j] = 1;
			else h[i][j] = h[i-1][j]+1,l[i][j]=max(l[i-1][j],ll);
		}
		for(int j=n;j>=1;j--)
		{
			if(a[i][j]) rr = j-1,r[i][j] = n;
			else r[i][j] = min(rr,r[i-1][j]);
			res = max(res,(r[i][j]-l[i][j]+1)*h[i][j]);
		}
	}
	cout<<res<<'\n';
	return 0;
 }

AC代码(上述代码优化为滚动数组)

#include<bits/stdc++.h>
using namespace std;
const int N = 2005;

int a[N][N];
int res,n;
int h[N],l[N],r[N];

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			cin>>a[i][j];
	
	for(int i=1;i<=n;i++)
	{
		int ll=1,rr=n;
		for(int j=1;j<=n;j++)
		{
			if(a[i][j]) ll = j+1,l[j] = 1,h[j]=0;
			else h[j]++,l[j]=max(l[j],ll);
		}
		for(int j=n;j>=1;j--)
		{
			if(a[i][j]) rr = j-1,r[j] = n;
			else r[j] = min(rr,r[j]);
			res = max(res,(r[j]-l[j]+1)*h[j]);
		}
	}
	cout<<res<<'\n';
	return 0;
 }

往期优质文章推荐

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-09-03 12:10:17  更:2021-09-03 12:10:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 2:03:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码