poj3384——半平面交+枚举(旋转卡壳)

题目链接:http://poj.org/problem?id=3384

Feng shui is the ancient Chinese practice of placement and arrangement of space to achieve harmony with the environment. George has recently got interested in it, and now wants to apply it to his home and bring harmony to it.

There is a practice which says that bare floor is bad for living area since spiritual energy drains through it, so George purchased two similar round-shaped carpets (feng shui says that straight lines and sharp corners must be avoided). Unfortunately, he is unable to cover the floor entirely since the room has shape of a convex polygon. But he still wants to minimize the uncovered area by selecting the best placing for his carpets, and asks you to help.

You need to place two carpets in the room so that the total area covered by both carpets is maximal possible. The carpets may overlap, but they may not be cut or folded (including cutting or folding along the floor border) — feng shui tells to avoid straight lines.

Input

The first line of the input file contains two integer numbers n and r — the number of corners in George’s room (3 ≤ n ≤ 100) and the radius of the carpets (1 ≤ r ≤ 1000, both carpets have the same radius). The following n lines contain two integers xi and yi each — coordinates of the i-th corner (−1000 ≤ xi, yi ≤ 1000). Coordinates of all corners are different, and adjacent walls of the room are not collinear. The corners are listed in clockwise order.

Output

Write four numbers x1, y1, x2, y2 to the output file, where (x1, y1) and (x2, y2) denote the spots where carpet centers should be placed. Coordinates must be precise up to 4 digits after the decimal point.

If there are multiple optimal placements available, return any of them. The input data guarantees that at least one solution exists.

Sample Input

 
#15 2
-2 0
-5 3
0 8
7 3
5 0
#24 3
0 0
0 8
10 8
10 0

Sample Output

 
#1-2 3 3 2.5
#23 5 7 3

Hint

题目翻译:

‎风水是中国古代空间布局和布置的实践,旨在与环境和谐相处。乔治最近对它产生了兴趣,现在想把它应用到他的家里,给家里带来和谐。‎

‎有一种做法认为,裸露的地板对生活区不利,因为精神能量会通过它排出,所以乔治买了两块类似的圆形地毯(风水说必须避免直线和尖角)。不幸的是,他无法完全覆盖地板,因为房间有凸面的形状。但他仍然想通过选择最适合他的地毯来最小化未覆盖的区域,并请求你帮忙。‎

‎您需要在房间里放置两块地毯,以便两块地毯覆盖的总面积是最大可能的。地毯可能会重叠,但不得切割或折叠(包括沿地板边框切割或折叠) - 风水告诉避免直线。‎

‎输入‎

‎输入文件的第一行包含两个整数‎‎n‎‎和‎‎r‎‎ = George 房间中的角数(3 = ‎‎n‎‎ = 100)和地毯半径(1 = ‎‎r‎‎ = 1000,两个地毯具有相同的半径)。以下‎‎n‎‎行包含两个整数‎‎x‎‎i‎‎和‎‎y‎‎i‎‎每个 = 坐标的‎‎i‎‎-th 角 (+1000 × ‎‎x‎‎i‎‎, ‎‎y‎‎i‎‎ ‎‎ = 1000)。所有角的坐标都不同,房间的相邻墙不是共线性的。角按顺时针顺序列出。‎

‎输出‎

‎将四个数字‎‎x‎‎1‎‎ ‎‎、y‎‎1、x‎‎ ‎‎ ‎‎2、y 2‎‎写入输出文件,其中‎‎(x‎‎1,‎‎ ‎‎y‎‎1)‎‎和 (‎‎x‎‎2‎‎,‎‎y‎‎2)‎‎表示应放置地毯中心的点。小数点之后的坐标必须精确到 4 位。‎

‎如果有多个最佳位置可用,则返回其中任何一个。输入数据保证至少存在一个解决方案。‎

题意理解。

给你一个n个点组成的多边形和一个半径为r的圆,要求在这个多边形里面放两个最小重叠的圆,以保证覆盖面积最大,求这两个圆的圆心位置。

首先对这个多边形内缩r(每条边平移r),然后再半平面交出一个凸多边形,圆心的范围就是这个凸多边形。因为保证最小重叠,所以需要两个圆心的距离尽量远,不难想到圆心位置就是这个凸包的最远点对,然后就可以用旋转卡壳(线性)或者直接枚举去求。

剩下的都是细节问题,不过这个题特判的有点毒瘤,精度卡的特别恶心,我用的大白书的板子,改的部分下面给出。

还有要注意半平面交后是一个点的问题。

bool OnLeft(Line L, Point p){
    return sgn(Cross(L.v,p-L.p))>=0;
	//保证精度 
}
if(fabs(Cross(q[lst].v, q[lst - 1].v))<eps){//这里改了,更保证精度准确 
            //两向量平行且同向,取内侧一个
            --lst;
            if(OnLeft(q[lst], L[i].p)) q[lst] = L[i];
        }

下面抛代码。

#include<iostream>
#include<cmath> 
#include<algorithm>
using namespace std;
const double eps = 1e-9;
struct Point{
    double x, y;
    Point(double x = 0, double y = 0):x(x),y(y){}
}p[205],poly[205];
typedef Point Vector;
Vector operator + (Vector A, Vector B){
    return Vector(A.x+B.x, A.y+B.y);
}
Vector operator - (Point A, Point B){
    return Vector(A.x-B.x, A.y-B.y);
}
Vector operator * (Vector A, double p){
    return Vector(A.x*p, A.y*p);
}
int sgn(double x){
    if(fabs(x) < eps)
        return 0;
    if(x < 0)
        return -1;
    return 1;
}
double Dot(Vector A, Vector B){
    return A.x*B.x + A.y*B.y;
}
double Cross(Vector A, Vector B){
    return A.x*B.y-A.y*B.x;
}
double Length(Vector A){
    return sqrt(Dot(A, A));
}
Vector Normal(Vector A){//向量A左转90°的单位法向量
    double L = Length(A);
    return Vector(-A.y/L, A.x/L);
}
double Dist(Point a,Point b){
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
struct Line{
    Point p;//直线上任意一点
    Vector v;//方向向量,它的左边就是对应的半平面
    double ang;//极角,即从x轴正半轴旋转到向量v所需要的角(弧度)
    Line(){}
    Line(Point p, Vector v) : p(p), v(v){
        ang = atan2(v.y, v.x);
    }
    bool operator < (const Line& L) const {//排序用的比较运算符
        return ang < L.ang;
    }
}L[105];
//点p在有向直线L的左侧
bool OnLeft(Line L, Point p){
    return sgn(Cross(L.v,p-L.p))>=0;
	//更精确 
}
//两直线交点。假定交点唯一存在
Point GetIntersection(Line a, Line b){
    Vector u = a.p - b.p;
    double t = Cross(b.v, u)/Cross(a.v, b.v);
    return a.p + a.v*t;
}
//半平面交的主过程
int HalfplaneIntersection(Line* L, int n, Point* poly){
    sort(L, L + n);//按照极角排序
    int fst = 0, lst = 0;//双端队列的第一个元素和最后一个元素
    Point *P = new Point[n];//p[i] 为 q[i]与q[i + 1]的交点
    Line *q = new Line[n];//双端队列
    q[fst = lst = 0] = L[0];//初始化为只有一个半平面L[0]
    for(int i = 1; i < n; ++i){
        while(fst < lst && !OnLeft(L[i], P[lst - 1])) --lst;
        while(fst < lst && !OnLeft(L[i], P[fst])) ++fst;
        q[++lst] = L[i];
        if(fabs(Cross(q[lst].v, q[lst - 1].v))<eps){//这里改了,更保证精度准确 
            //两向量平行且同向,取内侧一个
            --lst;
            if(OnLeft(q[lst], L[i].p)) q[lst] = L[i];
        }
        if(fst < lst)
            P[lst - 1] = GetIntersection(q[lst - 1], q[lst]);
    }
    while(fst < lst && !OnLeft(q[fst], P[lst - 1])) --lst;
    //删除无用平面
    if(lst - fst <= 1)
		return 0;	
	//空集
    P[lst] = GetIntersection(q[lst], q[fst]);//计算首尾两个半平面的交点
    //从deque复制到输出中
    int m = 0;
    for(int i = fst; i <= lst; ++i) poly[m++] = P[i];
    return m;
}
int main(){
	int n;
	double r;
	while(~scanf("%d%lf",&n,&r)){
		for(int i=0;i<n;i++)
			scanf("%lf%lf",&p[i].x,&p[i].y);
		p[n]=p[0];
		for(int i=0;i<n;i++){
			Vector v=p[i]-p[i+1];
			L[i]=Line(p[i]+Normal(v)*r,v);
		}
		int cnt=HalfplaneIntersection(L,n,poly);
		int maxi=0,maxj=0;
    	double maxn=0;
    	for(int i=0;i<cnt;i++){
    		for(int j=0;j<cnt;j++){
    			if(i==j) continue;
    			if(Dist(poly[i],poly[j])>maxn+eps){
    				maxn=Dist(poly[i],poly[j]);
    				maxi=i;
    				maxj=j;
				}
			}
		}
		printf("%.4f %.4f %.4f %.4f\n",poly[maxi].x,poly[maxi].y,poly[maxj].x,poly[maxj].y);
	}
	return 0;
}
/*
5 2
-2 0
-5 3
0 8
7 3
5 0

4 3
0 0 
0 8
10 8
10 0
*/ 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值