[HNOI2007][BZOJ1185] 最小矩形覆盖 [凸包+旋转卡壳]
题面
前置芝士
建议先学习向量相关的计算几何基础
计算几何基础戳这里
思路
用这道题学习一下凸包和旋转卡壳
首先是凸包部分
凸包
求凸包用的算法是graham算法
算法流程如下:
找到$y$坐标最小的一点作为原点
对原点之外的所有点按照到原点的极角排序(这里因为选取了最靠下的,所以极角范围在$[0,\pi]$)
依次遍历所有排序后的点,加入一个单调栈中:每次判断(栈顶元素和栈顶第二元素之间的斜率)是否大于(当前点和栈顶第二元素之间的斜率)
注意一旦这个大于成立了,栈顶元素就会在当前元素和栈顶第二元素的连线的“下面”,也就是在凸包里面了
因为我们事先按照极角排序了,所以这一单调栈可以不重复不遗漏地记录凸包上所有点
注意这样求出来的凸包上的点是逆时针排序的(根本原因是因为极角排序就是逆时针绕圈)
graham算法的复杂度是$O(n\log n)$,瓶颈是排序
旋转卡壳
首先,我在这道题里面用的不是标准的旋转卡壳算法......但是也是“旋转+卡壳”的思路
标准版的旋转卡壳戳这里,这里标准版指的是用4条边去卡壳,我写的是一条边和3个极值点
对于最小面积矩形,我们有结论:这一外接矩形一定有一条边和凸包的一条边重合
注意这个结论对于最小周长矩形依然成立
证明嘛......我愣是没找到。感性理解一下就是对于一个四边都接在凸包端点上的矩形,把它旋转一下一定更优
核心算法流程如下:
我们遍历凸包上的每一条边,并对于每一条边求出以这条边为x轴时,最靠左的点、最靠右的点、最靠上的点
设求出来的凸包上的点是$q[0...m-1]$
假设我们当前的边的两个端点是凸包上的$q[i],q[i+1]$,而且是有向的(i指向i+1),那么上述三个端点有如下性质:
对于最靠左的点$q[l]$,$vec(q[i],q[l])\ast vec(q[i],q[i+1])$是所有$l$中最小的
对于最靠右的点$q[r]$,$vec(q[i],q[r])\ast vec(q[i],q[i+1])$是所有$r$中最大的
对于最靠上的点$q[u]$,$vec(q[i],q[i+1])$叉乘$vec(q[i],q[u])$是所有$u$中最大的
其中$vec(u,v)$表示从点$u$指向$v$的向量
前两个的证明,利用点乘的性质:因为点乘的被投影向量长度相等,所以决定点乘结果大小的就是投影的大小
那么显然投影最小最靠左,投影最大最靠右
第三个的证明,利用叉乘的性质:叉乘等于两个向量逆时针旋转构成的有向平行四边形面积
因为平行四边形底边长度相同,而且$vec(q[i],q[i+1])$一定在所有从$q[i]$出发的向量的顺时针方向,所以反过来旋转一定是正的,叉乘最大就是最高
图示如下:

知道了这三个点以后,我们就可以知道这个矩形的长宽,进而求出面积了
又有性质:我们每次从$vec(q[i],q[i+1])$旋转到$vec(q[i+1],q[i+2])$的时候,$l,r,u$也会跟着逆时针旋转,所以只要枚举即可
这样,整个旋转卡壳就是“遍历所有边”+“顺序求出端点”的过程,总复杂度是$O(n)$的
Code
代码中node是向量结构体,对于点我们用位矢表示,也是向量
标$\ast$的是叉乘,标\的是点乘
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define eps 1e-9
#define ll long long
using namespace std;
inline int read(){
int re=0,flag=1;char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') flag=-1;
ch=getchar();
}
while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
struct node{
double x,y;
node(double xx=0.0,double yy=0.0){x=xx;y=yy;}
inline bool operator <(const node &b){return ((fabs(y-b.y)<eps)?(x<b.x):(y<b.y));}
inline friend bool operator ==(const node &a,const node &b){return ((fabs(a.x-b.x)<eps)&&(fabs(a.y-b.y)<eps));}
inline friend bool operator !=(const node &a,const node &b){return !(a==b);}
inline friend node operator +(const node &l,const node &r){return node(l.x+r.x,l.y+r.y);}
inline friend node operator -(const node &l,const node &r){return node(l.x-r.x,l.y-r.y);}
inline friend node operator *(node l,double r){return node(l.x*r,l.y*r);}
inline friend double operator *(const node &l,const node &r){return l.x*r.y-l.y*r.x;}
inline friend double operator /(const node &l,const node &r){return l.x*r.x+l.y*r.y;}
inline friend double dis(const node &a){return sqrt(a.x*a.x+a.y*a.y);}
}a[100010],q[100010],x[10];
int n,top;double ans=1e60;
inline bool cmp(node l,node r){
double tmp=(a[1]-l)*(a[1]-r);
if(fabs(tmp)<eps) return dis(a[1]-l)<dis(a[1]-r);
else return tmp>0;
}
void graham(){//get a counter-clockwise convex
int i;
for(i=2;i<=n;i++){
if(a[i]<a[1]) swap(a[1],a[i]);
}
sort(a+2,a+n+1,cmp);
q[++top]=a[1];
q[++top]=a[2];
for(i=3;i<=n;i++){
while(top>1&&((q[top]-q[top-1])*(a[i]-q[top])<eps)) top--;
q[++top]=a[i];
}
q[0]=q[top];
}
void RC(){//RotatingCalipers
int l=1,r=1,p=1,i;
double L,R,D,H,tmp;
for(i=0;i<top;i++){
D=dis(q[i]-q[i+1]);
while((q[i+1]-q[i])*(q[p+1]-q[i])-(q[i+1]-q[i])*(q[p]-q[i])>-eps) p=(p+1)%top;
while((q[i+1]-q[i])/(q[r+1]-q[i])-(q[i+1]-q[i])/(q[r]-q[i])>-eps) r=(r+1)%top;
if(i==0) l=r;
while((q[i+1]-q[i])/(q[l+1]-q[i])-(q[i+1]-q[i])/(q[l]-q[i])<eps) l=(l+1)%top;
L=(q[i+1]-q[i])/(q[l]-q[i])/D;
R=(q[i+1]-q[i])/(q[r]-q[i])/D;
H=(q[i+1]-q[i])*(q[p]-q[i])/D;
tmp=(R-L)*H;
if(tmp<ans){
ans=tmp;
x[0]=q[i]+(q[i+1]-q[i])*(R/D);
x[1]=x[0]+(q[r]-x[0])*(H/dis(x[0]-q[r]));
x[2]=x[1]-(x[0]-q[i])*((R-L)/dis(q[i]-x[0]));
x[3]=x[2]-(x[1]-x[0]);
}
}
}
int main(){
n=read();int i,j;
for(i=1;i<=n;i++) scanf("%lf%lf",&a[i].x,&a[i].y);
graham();
RC();
printf("%.5lf\n",ans);
j=0;
for(i=1;i<4;i++) if(x[i]<x[j]) j=i;
for(i=0;i<4;i++) printf("%.5lf %.5lf\n",x[j].x,x[j].y),j=(j+1)%4;
}
[HNOI2007][BZOJ1185] 最小矩形覆盖 [凸包+旋转卡壳]的更多相关文章
- bzoj 1185 [HNOI2007]最小矩形覆盖 凸包+旋转卡壳
题目大意 用最小矩形覆盖平面上所有的点 分析 有一结论:最小矩形中有一条边在凸包的边上,不然可以旋转一个角度让面积变小 简略证明 我们逆时针枚举一条边 用旋转卡壳维护此时最左,最右,最上的点 注意 注 ...
- [BZOJ1185][HNOI2007]最小矩形覆盖-[凸包+旋转卡壳]
Description 传送门 Solution 感性理解一下,最小矩形一定是由一条边和凸包上的边重合的. 然后它就是模板题了..然而真的好难调,小于大于动不动就打错. Code #include&l ...
- BZOJ1185[HNOI2007] 最小矩形覆盖(旋转卡壳)
BZOJ1185[HNOI2007] 最小矩形覆盖 题面 给定一些点的坐标,要求求能够覆盖所有点的最小面积的矩形,输出所求矩形的面积和四个顶点的坐标 分析 首先可以先求凸包,因为覆盖了凸包上的顶点,凸 ...
- BZOJ1185 [HNOI2007]最小矩形覆盖 【旋转卡壳】
题目链接 BZOJ1185 题解 最小矩形一定有一条边在凸包上,枚举这条边,然后旋转卡壳维护另外三个端点即可 计算几何细节极多 维护另外三个端点尽量不在这条边上,意味着左端点尽量靠后,右端点尽量靠前, ...
- 2018.10.18 bzoj1185: [HNOI2007]最小矩形覆盖(旋转卡壳)
传送门 不难看出最后的矩形一定有一条边与凸包某条边重合. 因此先求出凸包,然后旋转卡壳求出当前最小矩形面积更新答案. 代码: #include<bits/stdc++.h> #define ...
- bzoj1185【HNOI2007】最小矩形覆盖
1185: [HNOI2007]最小矩形覆盖 Time Limit: 10 Sec Memory Limit: 162 MBSec Special Judge Submit: 1114 Solv ...
- BZOJ1185 HNOI2007 最小矩形覆盖 凸包、旋转卡壳
传送门 首先,肯定只有凸包上的点会限制这个矩形,所以建立凸包. 然后可以知道,矩形上一定有一条边与凸包上的边重合,否则可以转一下使得它重合,答案会更小. 于是沿着凸包枚举这一条边,通过旋转卡壳找到离这 ...
- 【BZOJ1185】[HNOI2007]最小矩形覆盖(凸包,旋转卡壳)
[BZOJ1185][HNOI2007]最小矩形覆盖(凸包,旋转卡壳) 题面 BZOJ 洛谷 题解 最小的矩形一定存在一条边在凸包上,那么枚举这条边,我们还差三个点,即距离当前边的最远点,以及做这条边 ...
- 【旋转卡壳+凸包】BZOJ1185:[HNOI2007]最小矩形覆盖
1185: [HNOI2007]最小矩形覆盖 Time Limit: 10 Sec Memory Limit: 162 MBSec Special JudgeSubmit: 1945 Solve ...
随机推荐
- 我错了的N个学习
其实在面对自己的失误的时候,勇敢的说我错了,就是我错了,不找借口,不找理由,然后就开始分析错误的原因,分析,总结,学习,提高,成为自己成长的垫脚石,这个才是正确的做法,做人要拿出精神头,拼死至休的劲头 ...
- 一个只有十行的精简MVVM框架
本文来自网易云社区. 前言 MVVM模式相信做前端的人都不陌生,去网上搜MVVM,会出现一大堆关于MVVM模式的博文,但是这些博文大多都只是用图片和文字来进行抽象的概念讲解,对于刚接触MVVM模式的新 ...
- centos7下安装mysql8.0.12及设置权限
一.mysql版本介绍 mysql的官网为:https://www.mysql.com/ 在官网上可以看到多个版本,主要版本如下, 1.MySQL Community Server 社区版本,开源免费 ...
- 「日常训练」Jin Yong’s Wukong Ranking List(HihoCoder-1870)
题意与分析 2018ICPC北京站A题. 题意是这样的,给定若干人的武力值大小(A B的意思是A比B厉害),问到第几行会出现矛盾. 这题不能出现思维定势,看到矛盾就是矛盾并查集--A>B.A&g ...
- uvaoj 1081510815 - Andy's First Dictionary(set应用)
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=835&page= ...
- 【SpringCloud】 第九篇: 服务链路追踪(Spring Cloud Sleuth)
前言: 必需学会SpringBoot基础知识 简介: spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理.服务发现.断路器.路由.微代理.事件总线.全局锁.决策竞选. ...
- @meida 媒体查询
示例 @meida 媒体查询 在进行书写的时候需要考虑到加载顺序和样式权重使用meida响应式实现不同宽度布局示例 常用工具 https://mydevice.io 参考链接 https://deve ...
- 【QT】宏
宏 Q_CORE_EXPORT _CORE_EXPORT 其实是一个宏,用来说明这是一个动态库导出类.QT是个跨平台的库,而不同的操作系统,不同的编译器,对动态库的导出说明是不一样的,比如,在wind ...
- 352[LeetCode] Data Stream as Disjoint Intervals
Given a data stream input of non-negative integers a1, a2, ..., an, ..., summarize the numbers seen ...
- java超强分页标签演示
最近在做一个项目,用到了一个分页,于是动手写了个分页标签,先将代码贴出来,供大家交流,写的不好,请见谅!. 以下是java标签类,继承自SimpleTagSupport package com.lyn ...