poj 3525 半平面交求多边形内切圆最大半径【半平面交】+【二分】

<题目链接>

题目大意:
给出一个四面环海的凸多边形岛屿,求出这个岛屿中的点到海的最远距离。

解题分析:

仔细思考就会发现,其实题目其实就是让我们求该凸多边形内内切圆的最大半径是多少。但是,这个最大半径,没有什么比较好的求法,于是,我们可以想到二分答案求半径。对于二分的半径,我们可以将该凸多边形的边界向内平移 r 的距离,然后再用半平面交法,用这些平移后的直线去切割原凸多边形,如果最终切得的区域不为空,则二分枚举更大的半径,反之减小枚举的半径。知道恰好围成的区域为空(或恰好不为空)为止。

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

const double eps = 1e-8;
const double inf = 1e9;
const int MAXN = 210;
int m;//保存多边形的点数
double r;//保存内移距离
int cCnt, curCnt;//此时cCnt为最终切割得到的多边形的顶点数、暂存顶点个数

struct point
{
    double x, y;
};
point points[MAXN], p[MAXN], q[MAXN];//读入的多边形的顶点(顺时针)、p为存放最终切割得到的多边形顶点的数组、暂存核的顶点

void getline(point x, point y, double &a, double &b, double   &c) //两点x、y确定一条直线a、b、c为其系数
{
    a = y.y - x.y;
    b = x.x - y.x;
    c = y.x * x.y - x.x * y.y;
}

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

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);
    point pt;
    pt.x = (x.x * v + y.x * u) / (u + v);
    pt.y = (x.y * v + y.y * u) / (u + v);
    return  pt;
}

void cut(double a, double b, double c)               //利用半平面交求出切割后多边形的所有顶点
{
    curCnt = 0;
    for (int i = 1; i <= cCnt; ++i)             
    {
        if (a*p[i].x + b * p[i].y + c >= 0)q[++curCnt] = p[i];     // c因为精度问题,可能会偏小。所以有些点本应在右側而没在。
        else
        {
            if (a*p[i - 1].x + b * p[i - 1].y + c > 0)
            {
                q[++curCnt] = intersect(p[i], p[i - 1], a, b, c);
            }
            if (a*p[i + 1].x + b * p[i + 1].y + c > 0)
            {
                q[++curCnt] = intersect(p[i], p[i + 1], a, b, c);
            }
        }
    }

    for (int i = 1; i <= curCnt; ++i)p[i] = q[i];
    p[curCnt + 1] = q[1];
    p[0] = p[curCnt];
    cCnt = curCnt;
}

int dcmp(double x)    //控制精度
{
    if (fabs(x)<eps) return 0;
    else return x<0 ? -1 : 1;
}

void solve()
{
    initial();        //初始化存放多边形顶点的p数组

    for (int i = 1; i <= m; ++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);
    }
}

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

int main()
{
    while (scanf("%d", &m) != EOF) {
        if (m == 0) break;
        for (int i = 1; i <= m; i++)
            scanf("%lf%lf", &points[i].x, &points[i].y);
        Reverse();          //由于点的顺序是逆时针输入,所以要将它改成顺时针
        points[m + 1] = points[1];
        
        double left = 0, right = inf, mid;
        while ((right - left) >= eps) {        //二分求半径,eps控制二分的精度
            mid = (left + right) / 2.0;
            r = mid;                           //r为内切圆半径
            solve();
            if (cCnt <= 0) right = mid;     //如果将该多边形顶点向内平移r的距离后,半平面交所得多边形为空,则说明r过大,应当适当缩小
            else left = mid;
        }
        printf("%.6f\n", left);
    }
    return 0;
}

2018-08-03

猜你喜欢

转载自www.cnblogs.com/00isok/p/9416895.html