【枚举】 最大子矩阵(I)
题注:最大子矩形问题的解决办法最初由中国国家集训队王知昆前辈整理并发表为论文,在此说明并感谢。
Definition
给你一个大矩形,里面有一些障碍点,求一个面积最大的矩形,满足该矩形在大矩形内部且该矩形内部没有特殊点。矩形边界可以含有障碍点。这一类问题被称为最大子矩形问题。
Solution
首先引用一些由王知昆前辈定义的概念:
有效子矩形:合法的子矩形。
极大子矩形:对于一个有效子矩形,如果不存在完全包含它的有效子矩形,则该矩形是一个极大子矩形。
最大子矩形:所求的面积最大的有效子矩形
定理一:
一个最大子矩形一定是一个极大子矩形。
证明:
假设最大子矩形A不是极大子矩形,那么一定存在一个有效子矩形完全包含A,那么该矩形比A大且合法,则A不是最大子矩形。与假设矛盾。故一个最大子矩形一定是一个极大子矩形。
证毕。
定理二:
最大子矩形的四周一定不能再向外拓展。具体地,最大子矩形的四周边界上要么存在障碍点,要么是大矩形的边界。
证明:
假设最大子矩形的四周能向外扩展,那么它就不是一个极大子矩形。与【定理一】矛盾。
证毕。
这样,我们直接考虑枚举所有的障碍点作为矩形边界,判断它是不是一个合法的矩形,如果是,则更新答案。
记障碍点的个数是k,那么这么做的复杂度是\(O(k^5)\)。GG
考虑我们枚举每个点作为矩形的左边界,不断向右枚举它的右边界,能否顺手处理它的上下边界?
显然,对于左边界相同的矩形,右边界更靠右的矩形的上边界不高于靠左的矩形,下边界不低于更靠左的矩形。
那么我们可以每次枚举一个障碍点作为左边界,向右枚举右边界,每次更新上边界和下边界。
具体的方法为:每扫描到一个新的点,按照当前维护的上下边界计算面积并更新,更新后,如果当前点的竖直坐标高于左边界上的点,更新上边界,如果当前点的竖直坐标低于左边界上的点,更新右边界。
考虑特殊情况:如果两个点的竖直坐标相等,那么如何更新。
在这种情况下如果继续更新上边界或下边界,那么左边界就会同时作为上边界或下边界。如图:
将枚举分为两种情况:左侧障碍点只作为左边界;左侧障碍点同时作为左边界和上或下边界。
对于第一种情况,遇到竖直坐标相等直接break。因为后面不会产生左侧障碍只作为左边界的矩形。
对于第二种情况,每次扫描维护矩形向上拓展和向下拓展的最大值,不断更新。讲左侧障碍点作为上或下边界,不断更新面积。对于特殊情况,因为它在边界上对答案没有影响,直接忽略即可。
考虑是否将矩形枚举完全:我们只考虑了左边界是障碍点的情况,对于左边界是大矩形左侧的情况没有枚举到。
对于这种矩形,分成两种情况:只有左侧是大矩形边界,右侧是障碍点;左右两侧都是大矩形边界。
我们将大矩形四个角上四个点都加入到障碍点中,按照上面的算法,对于第一种情况可以通过被从右向左扫描一遍解决掉。
下面讨论第二种情况:
定理三:
一个左右边界都在大矩形的左右边界上的子矩形是极大子矩形充要条件是上下边界是按照竖直坐标排序后相邻的两个障碍点。
证明:
充分性:假设上下边界的障碍点不是相邻的,那么显然存在k在两个点之间,从而在矩形内部,矩形不合法。
必要性:假设子矩形的上下边界不是障碍点,那么它不是一个极大子矩形。
证毕。
那么我们将障碍点按照竖直坐标排序,则他们之间矩形的面积是相邻两个障碍点的高度差和大矩形横向长度的乘积。每次更新答案即可。
Example
description
由于John建造了牛场围栏,激起了奶牛的愤怒,奶牛的产奶量急剧减少。为了讨好奶牛,John决定在牛场中建造一个大型浴场。但是John的奶牛有一个奇怪的习惯,每头奶牛都必须在牛场中的一个固定的位置产奶,而奶牛显然不能在浴场中产奶,于是,John希望所建造的浴场不覆盖这些产奶点。这回,他又要求助于Clevow了。你还能帮助Clevow吗?
John的牛场和规划的浴场都是矩形。浴场要完全位于牛场之内,并且浴场的轮廓要与牛场的轮廓平行或者重合。浴场不能覆盖任何产奶点,但是产奶点可以位于浴场的轮廓上。
Clevow当然希望浴场的面积尽可能大了,所以你的任务就是帮她计算浴场的最大面积。
Input
输入文件的第一行包含两个整数L和W,分别表示牛场的长和宽。文件的第二行包含一个整数n,表示产奶点的数量。以下n行每行包含两个整数x和y,表示一个产奶点的坐标。所有产奶点都位于牛场内,即:\(0~\leq~x~\leq~L\),0\leqy\leqW。
Output
输出文件仅一行,包含一个整数S,表示浴场的最大面积。
Sample Input
10 10
4
1 1
9 1
1 9
9 9
Sample Output
80
Hint
\(0~\leq~n~\leq~5000\)
\(1~\leq~L,W~\leq~30000\)
Solution
板子题要啥solution?
Code
#include<cstdio>
#include<algorithm>
#define rg register
#define ci const int
#define cl const long long int
typedef long long int ll;
namespace IO {
char buf[100];
}
template <typename T>
inline void qr(T &x) {
char ch=getchar(),lst=' ';
while(ch>'9'||ch<'0') lst=ch,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(lst=='-') x=-x;
}
template <typename T>
inline void write(T x,const char aft,const bool pt) {
if(x<0) {putchar('-');x=-x;}
int top=0;
do {
IO::buf[++top]=x%10+'0';
x/=10;
}while(x);
while(top) putchar(IO::buf[top--]);
if(pt) putchar(aft);
}
template <typename T>
inline T mmax(const T _a,const T _b) {if(_b<_a) return _a;return _b;}
template <typename T>
inline T mmin(const T _a,const T _b) {if(_a>_b) return _b;return _a;}
template <typename T>
inline T mabs(const T _a) {if(_a<0) return -_a;return _a;}
template <typename T>
inline void mswap(T &_a,T &_b) {
T _temp=_a;_a=_b;_b=_temp;
}
const int maxn = 35010;
struct M {
int x,y;
inline M (int _a=0,int _b=0) {x=_a,y=_b;}
inline bool operator<(const M &_others) const {
return this->x<_others.x;
}
};
M MU[maxn];
inline bool cmp(const M &_a,const M &_b) {
return _a.y<_b.y;
}
int l,w,n;
int ans;
int main() {
qr(l);qr(w);qr(n);
for(rg int i=1;i<=n;++i) {
qr(MU[i].x);qr(MU[i].y);
}
MU[++n]=M(0,0);MU[++n]=M(l,0);MU[++n]=M(0,w);MU[++n]=M(l,w);
MU[0] = M(-1,-1);
std::sort(MU+1,MU+1+n,cmp);
for(rg int i=1;i<n;++i) {
rg int upceil = 0,downfloor = l;
rg int j=i+1;
while(MU[j].y==MU[i].y) ++j;
while(j<=n) {
ans=mmax(ans,(downfloor-upceil)*(MU[j].y-MU[i].y));
if(MU[j].x<MU[i].x) upceil=mmax(upceil,MU[j].x);
else if(MU[j].x>MU[i].x) downfloor=mmin(downfloor,MU[j].x);
else break;
++j;
}
j=i+1;upceil=0;downfloor=l;
while(MU[j].y==MU[i].y) ++j;
while(j<=n) {
ans=mmax(ans,(MU[j].y-MU[i].y)*mmax(MU[i].x-upceil,downfloor-MU[i].x));
if(MU[j].x<MU[i].x) upceil=mmax(upceil,MU[j].x);
else if(MU[j].x>MU[i].x) downfloor=mmin(downfloor,MU[j].x);
++j;
}
}
for(rg int i=n;i>1;--i) {
rg int upceil = 0,downfloor = l;
rg int j=i-1;
while(MU[j].y==MU[i].y) --j;
while(j) {
ans=mmax(ans,(downfloor-upceil)*(MU[j].y-MU[i].y));
if(MU[j].x<MU[i].x) upceil=mmax(upceil,MU[j].x);
else if(MU[j].x>MU[i].x) downfloor=mmin(downfloor,MU[j].x);
else break;
--j;
}
j=i-1;upceil=0;downfloor=l;
while(MU[j].y==MU[i].y) --j;
while(j) {
ans=mmax(ans,(MU[i].y-MU[j].y)*mmax(MU[i].x-upceil,downfloor-MU[i].x));
if(MU[j].x<MU[i].x) upceil=mmax(upceil,MU[j].x);
else if(MU[j].x>MU[i].x) downfloor=mmin(downfloor,MU[j].x);
--j;
}
}
rg int _temp=0;
std::sort(MU+1,MU+1+n);
for(rg int i=1;i<n;++i) {
_temp=mmax(_temp,MU[i+1].x-MU[i].x);
}
ans=mmax(ans,_temp*w);
write(ans,'\n',true);
}
Summary
这样的算法是有一定局限性的。当大矩形边界范围很大并且障碍点数较小时,使用本算法可以避免对坐标进行离散化降低代码难度并通过剪枝提升程序效率。但事实上,记大矩阵是 \(m~\times~n\)的矩阵,那么障碍点的个数上限会达到 \(m~\times~n\),\(m\)与\(n\)较大时讲难以接受。
存在一种只与矩形大小有关的算法。将在后面的blog介绍。
再次感谢王知昆前辈
【枚举】 最大子矩阵(I)的更多相关文章
- Hihocoder 1634 Puzzle Game(2017 ACM-ICPC 北京区域赛 H题,枚举 + 最大子矩阵变形)
题目链接 2017 Beijing Problem H 题意 给定一个$n * m$的矩阵,现在可以把矩阵中的任意一个数换成$p$,求替换之后最大子矩阵的最小值. 首先想一想暴力的方法,枚举矩阵中 ...
- 崩 oj 1768 最大子矩阵
描述 已知矩阵的大小定义为矩阵中所有元素的和.给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵.比如,如下4 * 4的矩阵0 -2 -7 0 9 2 -6 2 -4 1 - ...
- noi openjudge 1768:最大子矩阵
链接:http://noi.openjudge.cn/ch0406/1768/ 描述已知矩阵的大小定义为矩阵中所有元素的和.给定一个矩阵,你的任务是找到最大的非空(大小至少是1 * 1)子矩阵. 比如 ...
- [VijosP1764]Dual Matrices 题解
题目大意: 一个N行M列的二维矩阵,矩阵的每个位置上是一个绝对值不超过1000的整数.你需要找到两个不相交的A*B的连续子矩形,使得这两个矩形包含的元素之和尽量大. 思路: 预处理,n2时间算出每个点 ...
- 【题解】Sonya and Matrix Beauty [Codeforces1080E]
[题解]Sonya and Matrix Beauty [Codeforces1080E] 传送门:\(Sonya\) \(and\) \(Matrix\) \(Beauty\) \([CF1080E ...
- cogs 997. [東方S2] 射命丸文
二次联通门 : cogs 997. [東方S2] 射命丸文 /* cogs 997. [東方S2] 射命丸文 二维前缀和 枚举每个子矩阵 更新最大值.. 莫名rank1 */ #include < ...
- 51nod 1051
* 最大子矩阵 * sum[i][j] 表示第 i 行前 j 列的和,即每一行的前缀 * i,j 指针枚举列,k指针枚举行 * Now 记录当前枚举的子矩阵的价值 * 由于记录了前缀信息,一旦 Now ...
- 一些简单题(1)(Source : NOIP历年试题+杂题)
最近也写了些许题目吧,还是写写博客,捋捋思路. P2216 [HAOI2007]理想的正方形 求一个$a \times b(a,b \leq 10^3)$的矩阵,求出一个$n \times n (n ...
- Codeforces Round #524 (Div. 2) codeforces 1080A~1080F
目录 codeforces1080A codeforces 1080B codeforces 1080C codeforces 1080D codeforces 1080E codeforces 10 ...
- 2019杭电暑假多校训练 第六场 Snowy Smile HDU - 6638
很多题解都是简单带过,所以打算自己写一篇,顺便也加深自己理解 前置知识:线段树.线段树维护最大字段和.二维坐标离散化 题解: 1.很容易想到我们需要枚举所有子矩阵来得到一个最大子矩阵,所以我们的任务是 ...
随机推荐
- 原生js实现轮播图原理
轮播图的原理1.图片移动实现原理:利用浮动将所有所有照片依次排成一行,给这一长串图片添加一个父级的遮罩,每次只显示一张图,其余的都隐藏起来.对图片添加绝对定位,通过控制left属性,实现照片的移动. ...
- leetcode-电话号码的字母组合
电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合. 给出数字到字母的映射如下(与电话按键相同).注意 1 不对应任何字母. 示例: 输入:"23" ...
- Dreamweaver CS5网页制作教程
说到Dreamweaver这个网页制作神器,不由得想起在学校里上的选修课,那是的我们只知道 table 布局,只知道构建网站最方便的是使用“所见即所得”编辑器.回忆一下,真的是很怀旧啊! 虽说咱现在大 ...
- JDK源码分析:Short.java
Short是基本数据类型short的包装类. 1)声明部: public final class Short extends Number implements Comparable<Short ...
- 【shell 练习3】用户管理脚本(一)
一.创建十个用户,密码为八位 [root@localhost ~]# cat UserManger02.sh #!/bin/bash . /etc/init.d/functions [ $UID -n ...
- Python3 Tkinter-Listbox
1.创建 from tkinter import * root=Tk() lb=Listbox(root) for item in ['python','tkinter','widget']: lb. ...
- centos+nginx+redmine+gitosis安装指南
说明 这篇文章我现在的主要目的是记录自己安装redmine和gitosis的过程,可能写的有些糙,请各位读者见谅.我会在后面的时间里逐渐完善细节.但我想,这已经是网上迄今为止国内最详细的nginx+r ...
- svn服务器 备份,迁移,部署方案
这次做业务迁移,要从一个云厂商迁移到某云厂商,之前每天到全备svn排到用场了,需要搭建一个全新到svn服务并要做迁移,并实现我们开发机到时时代码同步 一.svn备份有很多种,优劣都不同,百度可查,我采 ...
- 软工第三次作业——个人PSP
9.22--9.26本周例行报告 1.PSP(personal software process )个人软件过程. 类型 任务 预计时间 开始时间 结束时间 中断时间 实际用时 准备工作 学习重定向 ...
- M2迭代分数分配
M2中仍然依据每个成员的工作量,贡献度分配相应得分. 成员 得分 申开亮 25 王皓南 24 许晋 21 黄玉冰 20 王宇杰 18 吴润凡 17 巴丹益昔 15