• 半平面交笔记


    什么是半平面交

    • 一条有向直线会把平面分为两部分,若规定逆时针方向为正方向,则该有向直线逆时针方向的那一平面就是这条直线的半平面。

    阴影部分就是这条直线的半平面。

    • 而给出若干条有向直线并规定正方向以后,这些直线的半平面的交集就是这些直线的半平面交。

    半平面交用来做什么

    • 给出一个多边形,求它的核。核是该多边形内的一个凸多边形,核内的每个点都能看到多边形的每个角落。相当于核内任意一点与多边形上一点的连线都在多边形内。

    形象的理解,多边形是一个房间,核是里面的一片区域,在这片区域里装摄像头,能监控到整个多边形。

    半平面交的求法

    只会S&I算法,当然这个算法也是最好的半平面交算法,好写,效率也很高。

    计算几何基础

    由于本人太菜了,需要总结一下计算几何基础。

    直线表示

    由于半平面交中直线是有向的,为了表示这样的直线,需要两个有序点对(s=(x1,y1), t=(x2,y2)),表示直线由(s)(t)。此外半平面交中还需按极角排序,因此还需记录极角(一般规定极角为该直线正方向对应的射线与x轴正方向对应的射线的夹角)。

    浮点比较

    const eps = 1e-6;
    int dcmp(double a, double b) { return fabs(a - b) < eps; }
    

    直线交点

    按照上面这种表示法,求两条直线的交点应该是这样的(cross是向量叉积):

    POINT inter(LINE a, LINE b) { return a.s + (a.t - a.s) * (cross(b.t - b.s, a.s - b.s) / cross(a.t - a.s, b.t - b.s)); }
    

    这个方法画图出来就很容易理解了。

    在直线左or右

    判断一个点是否在直线右边:

    int onright(POINT a, LINE b) { return cross(b.t - b.s, a - b.s) < 0; }
    

    利用了有向面积的正负性判断。

    进入正题

    S&I算法的大体步骤

    • 将输入直线按照极角从小到大排序
      Tips: 有些直线的极角大小是相等的,如果逆时针为正方向,那么显然位置较右的直线是没有用的,这时我们只保留最左边的直线。
    • 开两个双端队列q1,q2,q1用来存半平面交上的直线,q2用来存半平面交上的点。
    • 将第一条直线加入q1。
    • 依次加入每条直线l[i],若q1中直线个数>1,则比较q2末尾的点是否在l[i]右边,是则弹出q1队尾的直线以及q2队尾的点,如此循环,直到不能删为止。
    • 若q1中直线个数>1,则比较q2队头的点是否在l[i]右边,是则弹出q1队头的直线以及q2队头的点,如此循环,直到不能删为止。
    • 将l[i]入队,算出l[i]与q1队尾直线的交点,将该交点加入q2队尾。
    • 做完所有直线以后,检查q2队尾的点是否在q1队头直线右边,是则弹出q1队尾的直线以及q2队尾的点,如此循环,直到不能删为止。
    • 检查q2队头的点是否在q1队尾直线右边,是则弹出q1队头的直线以及q2队头的点,如此循环,直到不能删为止。
    • 此时q1中的直线即为半平面交的每条边,q2即为半平面交上的点(还要加上最后加入的直线与最早加入的直线的交点)。

    流程解释

    • 为什么要排序?
      排序后我们就是在逆时针地加入每条直线,这样直线间关系就存在了单调性,保证了后面过程的正确性。
    • 为什么要用双端队列?
      因为求半平面交的过程实际上是环形的,新加入的直线不仅会影响到最后加入的直线,也会影响到最早加入的直线。

      依次加入直线AB,直线CD,直线EF,直线HG
      AB,CD的交点在HG右侧,那么此时最早加入的直线AB已无用处,将它出队。

    代码

    poj3130 判断多边形是否有核

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    typedef double db;
    const int N = 57;
    const db eps = 1e-6;
    
    int dcmp(db a, db b) { return fabs(a - b) < eps; } //浮点数比较相等
    struct VECTOR
    {
    	db x, y;
    	VECTOR(db X = 0, db Y = 0) { x = X, y = Y; }
    	db getang() { return atan2(y, x); } //求极角
    };
    db cross(VECTOR a, VECTOR b) { return a.x * b.y - a.y * b.x; } //叉积
    VECTOR operator+(VECTOR a, VECTOR b) { return VECTOR(a.x + b.x, a.y + b.y); } //基本向量运算
    VECTOR operator-(VECTOR a, VECTOR b) { return VECTOR(a.x - b.x, a.y - b.y); }
    VECTOR operator*(VECTOR a, db b) { return VECTOR(a.x * b, a.y * b); }
    typedef VECTOR POINT;
    struct LINE
    {
    	POINT s, t;
    	db ang;
    	LINE() {}
    	LINE(POINT S, POINT T) { s = S, t = T, ang = (t - s).getang(); } //根据起点终点构造直线
    };
    int operator<(LINE a, LINE b) { return dcmp(a.ang, b.ang) ? cross(b.t - a.s, a.t - a.s) + eps > 0 : a.ang < b.ang; } //比较极角,极角相等时保证最左边的直线排的最前
    POINT inter(LINE a, LINE b) { return a.s + (a.t - a.s) * (cross(b.t - b.s, a.s - b.s) / cross(a.t - a.s, b.t - b.s)); } //计算两直线的交点
    int onright(POINT a, LINE b) { return cross(b.t - b.s, a - b.s) < 0; } //判断一个点是否在某直线右边
    
    int n;
    POINT p[N];
    LINE l[N];
    
    int h, t;
    LINE q1[N];
    POINT q2[N];
    int SI()
    {
    	sort(l + 1, l + n + 1); //排序
    	h = 1, q1[t = 1] = l[1];
    	for (int i = 2; i <= n; i++)
    	{
    		if (dcmp(l[i].ang, l[i - 1].ang)) continue; //多条极角相同的直线,取最左边的一条
    		while (h < t && onright(q2[t - 1], l[i])) t--; //删掉队尾无用直线
    		while (h < t && onright(q2[h], l[i])) h++; //删掉队头无用直线
    		q1[++t] = l[i];
    		if (h < t) q2[t - 1] = inter(q1[t], q1[t - 1]); //加入交点
    	}
    	while (h < t && onright(q2[t - 1], q1[h])) t--; //最后检查
    	while (h < t && onright(q2[h], q1[t])) h++;
    	if (t - h <= 1) return 0; //半平面交上的点少于3个,无法构成多边形,该多边形的核不存在
    	return 1;
    }
    
    int main()
    {
    	while (1)
    	{
    		scanf("%d", &n);
    		if (!n) break;
    		for (int i = 1; i <= n; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
    		l[1] = LINE(p[n], p[1]); //输入保证该线段左侧是多边形的内部,直接连线即可
    		for (int i = 2; i <= n; i++) l[i] = LINE(p[i - 1], p[i]);
    		printf(SI() ? "1
    " : "0
    ");
    	}
    	return 0;
    }
    

    一道例题 from SDOI2013

  • 相关阅读:
    细说google,baidu引擎收录习惯
    考研机试 11.二叉树遍历
    考研机试 18.特殊乘法
    考研机试 19.今年的第几天
    考研机试 20.完数VS盈数
    考研机试 12.玛雅人的密码
    考研机试 10.球的半径和体积
    考研机试 17.n的阶乘
    考研机试 15.abc
    考研机试 9.成绩排序
  • 原文地址:https://www.cnblogs.com/zjlcnblogs/p/11116487.html
Copyright © 2020-2023  润新知