【HNOI2007】最小矩形覆盖

本文介绍了一种解决几何问题的方法,即如何找到覆盖一组特定点集的最小面积矩形。通过构建凸包并使用旋转卡壳技巧,文章详细阐述了算法的设计思路与实现细节。

这里写图片描述
这里写图片描述

题解

比较显然的一道几何题,思维过程也比较简单,关键点在于想清楚最小矩形的一条边一定在凸包上(卡得最紧)。建好凸包后,在其上旋转卡壳,维护矩形另外三条边需经过的三个点。恶心的地方在于卡精度(在1*1的范围内竟然有万多个点),不知为何,在同样的计算方法下,我把凸包最后一个点提到最前讨论就过了……

代码

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
using namespace std;
typedef double db;
const int maxn=5e4+5;
const db eps=1e-8;
int n,top,st=1;
db ans=1e60;
struct Point
{
    db x,y;
    Point(){}
    Point(db x,db y):x(x),y(y){}
}p[maxn],s[maxn],ver[5];
typedef Point Vector;
int dcmp(db a)
{
    if(fabs(a)<eps) return 0;
    return a<0?-1:1;
}
bool operator < (const Point& a,const Point& b)
{
    return dcmp(a.x-b.x)<0||(dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)<0);
}
Point operator + (const Point& a,const Vector& b)
{
    return Point(a.x+b.x,a.y+b.y);
}
Vector operator - (const Point& a,const Point& b)
{
    return Vector(a.x-b.x,a.y-b.y);
}
Vector operator * (const Vector& a,const db& b)
{
    return Vector(a.x*b,a.y*b);
}
Vector operator / (const Vector& a,const db& b)
{
    return Vector(a.x/b,a.y/b);
}
db Cross(Vector a,Vector b)
{
    return a.x*b.y-a.y*b.x;
}
db Dot(Vector a,Vector b)
{
    return a.x*b.x+a.y*b.y;
}
db Length(Vector a)
{
    return sqrt(Dot(a,a));
}
void Andrew()
{
    sort(p+1,p+1+n),s[++top]=p[1];
    for(int i=2;i<=n;i++)
    {
        while(top>1&&dcmp(Cross(s[top]-s[top-1],p[i]-s[top-1]))<=0) top--;
        s[++top]=p[i];
    }
    int temp=top;
    for(int i=n-1;i;i--)
    {
        while(top>temp&&dcmp(Cross(s[top]-s[top-1],p[i]-s[top-1]))<=0) top--;
        s[++top]=p[i];
    }
    top--,s[0]=s[top];
}
void RC()
{
    for(int i=0,j=1,l=1,r=1;i<top;i++)
    {
        Vector v=s[i+1]-s[i],w;
        db dis=Length(v);
        while(dcmp(Cross(v,s[j+1]-s[i])-Cross(v,s[j]-s[i]))>=0) j=j%top+1;
        while(dcmp(Dot(v,s[r+1]-s[i])-Dot(v,s[r]-s[i]))>=0) r=r%top+1;
        l=j;
        while(dcmp(Dot(v,s[l+1]-s[i])-Dot(v,s[l]-s[i]))<=0) l=l%top+1;
        db h=fabs(Cross(v,s[j]-s[i]))/dis;
        db wl=Dot(v,s[l]-s[i])/dis,wr=Dot(v,s[r]-s[i])/dis;
        db ww=wr-wl,area=ww*h;
        if(area<ans)
        {
            ans=area;
            ver[1]=s[i]+v*(wr/dis);
            w=s[r]-ver[1],ver[2]=ver[1]+w*(h/Length(w));
            ver[3]=ver[2]-v*(ww/dis);
            ver[4]=ver[1]+(ver[3]-ver[2]);
        }
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
    Andrew(),RC();
    printf("%.5lf\n",ans);
    for(int i=1;i<=4;i++)
    {
        if(dcmp(ver[i].y-ver[st].y)<0||(dcmp(ver[i].y-ver[st].y)==0&&dcmp(ver[i].x-ver[st].x)<0)) st=i;
        ver[i+4]=ver[i];
    }
    for(int i=1;i<=4;i++) printf("%.5lf %.5lf\n",ver[i+st-1].x,ver[i+st-1].y);
    return 0;
}
4.4 激光炸弹 题⽬来源: 洛⾕ 题⽬链接: [HNOI2003]激光炸弹 难度系数: ★★ 【题⽬描述】 ⼀种新型的激光炸弹,可以摧毁⼀个边⻓为 R 的正⽅形内的所有的⽬标。 现在地图上有 个⽬标,⽤整数 , (其值在 )表⽰⽬标在地图上 的位置,每个⽬标都有⼀个价值。 N(1 ≤ N ≤ 10000) Xi Yi [0, 5000] 激光炸弹的投放是通过卫星定位的,但其有⼀个缺点,就是其爆破范围,即那个边⻓为 的正⽅形 的边必须和 轴平⾏。 R x, y 若⽬标位于爆破正⽅形的边上,该⽬标将不会被摧毁。 对于 的数据,保证 。 100% 1 ≤ n ≤ 10 4 , 0 ≤ xi, yi ≤ 5 × 10 3 , 1 ≤ m ≤ 5 × 10 3 , 1 ≤ vi ≤ 100 【输⼊描述】 输⼊⽂件的第⼀⾏为正整数 和正整数 ,接下来的 ⾏每⾏有 个正整数,分别表⽰ 。 N R N 3 xi, yi, vi 【输出描述】 输出⽂件仅有⼀个正整数,表⽰⼀颗炸弹最多能炸掉地图上总价值为多少的⽬标(结果不会超过 32767 )。 【⽰例⼀】 输⼊: 2 1 27 28 29 30 31 32 33 34 35 0 0 1 1 1 1 输出: 1 【解法】 可以⽤⼀个⼆维矩阵将所有⽬标的价值存起来,其中 a[i][j] 就表⽰ [i, j] 位置的⽬标价值之和。 ⼀颗炸弹能够获得的价值正好是⼀个 ⼤⼩的⼀个正⽅形内所有⽬标的价值总和,那么我们可 以求出 矩阵的前缀和矩阵,然后枚举所有边⻓为 的⼦正⽅形的价值之和,求出⾥⾯的最⼤值即 可。 R × R a R 如何枚举边⻓为 R 的所有正⽅形: • 仅需枚举右下⻆ ,那么结合边⻓ 就可算 出左上⻆ 。 [x2 , y2 ] (R + 1 ≤ x2 ≤ 5000, R + 1 ≤ y2 ≤ 5000) R [x2 − R + 1, y2 − R + 1] • 代⼊前缀和矩阵中,就可以快速求出这个正⽅形内所有⽬标的总价值。 细节问题: 1. 题⽬中某⼀个位置会「重复」出现,因此 a[i][j]+ = w ; 2. 半径 R 可能「超过 5000 」 ,此时炸弹可以摧毁所有⽬标,也就是整个矩阵的⽬标价值之和。 【参考代码】 代码块 #include <iostream> using namespace std; const int N = 5010; int n, m; int a[N][N]; int f[N][N]; // 前缀和矩阵 int main() { cin >> n >> m; while(n--) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { int x, y, v; cin >> x >> y >> v; x++, y++; // 下标从 1 开始计数 a[x][y] += v; // 同⼀个位置有可能有多个⽬标 } n = 5001; // 预处理前缀和矩阵 for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + a[i][j]; } } int ret = 0; m = min(m, n); // 如果 m 很⼤,相当于就是把整个区域全部摧毁 // 枚举所有边⻓为 m 的正⽅形 for(int x2 = m; x2 <= n; x2++) { for(int y2 = m; y2 <= n; y2++) { int x1 = x2 - m + 1, y1 = y2 - m + 1; ret = max(ret, f[x2][y2] - f[x1 - 1][y2] - f[x2][y1 - 1] + f[x1 - 1][y1 - 1]); } } cout << ret << endl; return 0; }
最新发布
09-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值