Poj 3384 Feng Shui (半平面求交应用)

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

题意:两个圆半径均为r(可重合但不能超出多边形边界)覆盖一个多边形,覆盖的面积最大时求两点坐标。

思路:若多边形足够大,则随便放,不重叠即可。若多边形较小,则需圆与多边形相切且尽量拉大圆心距。

方法是把所有边向内平移r(半径),然后半平面交,则两圆心在半平面交集的一对最远点上。


这个题貌似测试数据比较奇葩。。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const double EPS=1e-11;
const int NUM=1510;

int DB (double x)
{
    if (x>EPS)
        return 1;
    if (x<-EPS)
        return -1;
    return 0;
}

struct Point
{
    double x,y;
 
    Point(){}
    Point(double _x,double _y)
    {
        x=_x;
        y=_y;
    }
 
    void get ()
    {
		scanf("%lf%lf",&x,&y);
	}

    void Out ()
    {
        printf("%.4lf %.4lf",x,y);
    }
}points[NUM],p[NUM],q[NUM];

int n,r;
int cCnt,curCnt;

void getline (Point x,Point y,double &a,double &b,double &c)
{
	a = y.y - x.y;
	b = x.x - y.x;
	c = y.x * x.y - x.x * y.y;
}

Point intersect (Point x,Point y,double a,double b,double c)  //相交
{
    double u = fabs(a * x.x + b * x.y + c);
    double v = fabs(a * y.x + b * y.y + c);
    return Point( (x.x * v + y.x * u) / (u + v) , (x.y * v + y.y * u) / (u + v) );
}

/*半平面相交(直线切割多边形)(点标号从1开始)*/
void cut (double a,double b ,double c)
{
	int i;
	curCnt = 0;
	for (i=1;i<=cCnt;i++)
	{
        if (DB(a*p[i].x + b*p[i].y + c) >= 0)
			q[++curCnt] = p[i];
        else
		{
            if (DB(a*p[i-1].x + b*p[i-1].y + c) > 0)
                q[++curCnt] = intersect(p[i],p[i-1],a,b,c);
            if (DB(a*p[i+1].x + b*p[i+1].y + c) > 0)
                q[++curCnt] = intersect(p[i],p[i+1],a,b,c);
        }
    }
    for (i=1;i<=curCnt;i++)
		p[i] = q[i];
    p[curCnt+1] = q[1];
	p[0] = p[curCnt];
    cCnt = curCnt;
}

void GuiZhengHua ()
{
	//规整化方向,逆时针变顺时针,顺时针变逆时针
	for (int i=1;i<(n+1)/2;i++)
		swap(points[i], points[n-i]);
}

void initial ()
{
    for (int i=1;i<=n;i++)
		p[i] = points[i];  
	p[n+1] = p[1];
	p[0] = p[n];
	cCnt = n;
}

double dis (Point a,Point b)
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

void Deal ()
{
	int i,j;
	
	//注意:默认点是顺时针,如果题目不是顺时针,规整化方向  
	initial ();
    for (i=1;i<=n;i++)
	{
		Point ta, tb, tt; 
        tt.x = points[i+1].y - points[i].y; 
        tt.y = points[i].x - points[i+1].x; 
        double k = r / sqrt(tt.x * tt.x + tt.y * tt.y); 
        tt.x = tt.x * k; 
        tt.y = tt.y * k; 
        ta.x = points[i].x + tt.x; 
        ta.y = points[i].y + tt.y; 
        tb.x = points[i+1].x + tt.x; 
        tb.y = points[i+1].y + tt.y; 
        double a,b,c; 
        getline(ta,tb,a,b,c); 
        cut(a,b,c); 
    }
	
	Point ansa,ansb;
	double dmax=-1;
	for (i=1;i<=cCnt;i++)
		for (j=1;j<=cCnt;j++)      //不能写成j=i+1,因为可能存在平移后退化成点
			if (dis(p[i],p[j])>dmax)
			{
                 dmax=dis(p[i],p[j]);
                 ansa=p[i];
                 ansb=p[j];
			}
	printf("%.4lf %.4lf %.4lf %.4lf\n",ansa.x,ansa.y,ansb.x,ansb.y);
} 

int main ()
{
	while (~scanf("%d%d",&n,&r))
	{
		for (int i=1;i<=n;i++)
			points[i].get();
		points[0]=points[n];
		points[n+1] = points[1];
		Deal ();
	}
	return 0;
}

/*
Input
4 1
0 0
0 2
2 2
2 0

Output
1.0000 1.0000 1.0000 1.0000


4 1
1 1
1 -1
-1 -1
-1 1

0.0000 0.0000 0.0000 0.0000
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值