半平面求交 模板
半平面求交这么高大上的名字, 其实就是直线求切割一块区域.
半平面就是一条直线把一个平面分成两部分, 直线的一侧的平面就是半平面,所以半平面就用直线来表示, 其实这个跟高中学的那个线性规划类似. 线性规划是给出几个直线,求这个几个直线围城的区域, 同理, 半平面也是这样的.
典型的例题是: 给出一个多边形(不一定非得是凸的,有可能是凹的), 让求在这个多边形里面安装一个摄像头, 是否有个位置一定可以拍到所有的地方.
其实这个题让求的就是这个多边形的凸核, 凸核就是在这块凸型区域里面都可以拍到所有的情况. 那么现在问题就简单了,只要求出来凸核就行了. 凸核的求法其实也是挺简单的
步骤:
1. 首先你可以假设一个大平面(这里是为了好理解,其实做题的时候是直接就是原来的多边形就行了)为正方形,那么他会有四个定点, 把这四个顶点加入到点集中去,
2. 然后遍历给的多边形的每一条边, 让每一条边去切割这个大平面, 这个边可以用向量来表示, 它是有方向的, 如果顺时针遍历的话,那么只要在向量右侧的点和边上的点保留,切割到左侧的点, 因为顺时针旋转的话,左侧就是多边形的外面, 所以要切割掉, 如果是左侧的点话,找他的上一个顶点和下一个顶点,如果他们在这个向量里面的话,就要求出这个点和刚刚在外面的点组成的线段和切割向量所在直线的交点,然后把它加到点集中去. 如果不在,直接跳过
3. 重复2过程,直到每一条直线都切割这个平面了. 到最后如果这个点集 中没有点的话就说明不存在这个区域, 否则点集 中的点就是该区域的所有顶点
下面给出两种模板,一种是用向量的,一种是用直线方程的(其实这个直线还是有方向的),
还有poj买一赠二的题 poj 1474, poj 3335, poj 1279
poj 1474 (向量法)
/************************************************************************* > File Name: poj_1474.cpp > Author: Howe_Young > Mail: 1013410795@qq.com > Created Time: 2015年05月03日 星期日 16时05分05秒 ************************************************************************/ #include <cstdio> #include <iostream> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> #define EPS 1e-8 using namespace std; const int maxn = 500; struct point{ double x, y; }; struct segment{ point s, e; }; point p[maxn], pp[maxn], points[maxn];//p来存放新切割出来的多边形,pp来存放临时切割的多边形,points存放最原始的多边形 int n, ctot, kase;//n是初始的多边形总顶点数, ctot是新切割出来的多边形的顶点数 int sgn(double x) { if (fabs(x) < EPS) return 0; return x < 0 ? -1 : 1; } double x_multi(point p1, point p2, point p3)//向量叉乘 { return (p3.x - p1.x) * (p2.y - p1.y) - (p2.x - p1.x) * (p3.y - p1.y); } bool outside(segment seg, point p)//判断点是否在线段的外面, 严格外面,不包括边界 { return x_multi(seg.s, seg.e, p) < -EPS; } bool inside(segment seg, point p)//判断点是否在线段的里面, 严格里面,不包括边界 { return x_multi(seg.s, seg.e, p) > EPS; } point get_intersection(point p1, point p2, point p3, point p4)//获得两直线的p1p2和p3p4的交点 { double a1, b1, c1, a2, b2, c2; point tmp; a1 = (p2.y - p1.y); b1 = (p1.x - p2.x); c1 = (p2.x * p1.y - p1.x * p2.y); a2 = (p4.y - p3.y); b2 = (p3.x - p4.x); c2 = (p3.y * p4.x - p4.y * p3.x); tmp.x = (b1 * c2 - b2 * c1) / (b2 * a1 - b1 * a2); tmp.y = (a1 * c2 - c1 * a2) / (a2 * b1 - a1 * b2); return tmp; } //用线段seg来切割剩下的多边形(其实是p存放的多边形) void cut(segment seg) { int i, tot = 0; for (i = 1; i <= ctot; i++) { if (!outside(seg, p[i]))//如果当前点不在这条直线的外面的话,包括在内部和在边界上, 这时候不需要切割,直接把点加入pp内 pp[++tot] = p[i]; else//如果在外面的话,找他的两个相邻顶点 { if (inside(seg, p[i - 1]))//找它的上一个顶点是否在多边形的内部,边界上不算 pp[++tot] = get_intersection(seg.s, seg.e, p[i], p[i - 1]);//将它和这条线段的交点加入pp内 if (inside(seg, p[i + 1]))//同理 pp[++tot] = get_intersection(seg.s, seg.e, p[i], p[i + 1]); } } ctot = tot;//更新剩下的多边形的定点数目 pp[tot + 1] = pp[1]; pp[0] = pp[tot]; memcpy(p, pp, sizeof(pp));//将pp拷贝到p中 } void init() { for (int i = 1; i <= n; i++) p[i] = points[i];//刚开始剩余的多边形,还是整个多边形 p[0] = p[n]; p[n + 1] = p[1]; ctot = n;//刚开始的顶点数等于n } void slove() { init(); segment tmp; points[n + 1] = points[1];//至关重要! for (int i = 1; i <= n; i++)//让每条边都切割一下原来的多边形 { tmp.s = points[i]; tmp.e = points[i + 1]; cut(tmp); } if (ctot == 0)//如果最后不存在多边形,连一个点都没有,那么就不存在这个凸核 { printf("Floor #%d\nSurveillance is impossible.\n\n", ++kase); } else printf("Floor #%d\nSurveillance is possible.\n\n", ++kase); } int main() { while (~scanf("%d", &n) && n) { for (int i = 1; i <= n; i++) scanf("%lf %lf", &points[i].x, &points[i].y); slove(); } return 0; }
poj 1474(直线法)
#include <iostream> #include <algorithm> #include <cmath> #include <stdio.h> using namespace std; #define exp 1e-8 struct point { double x; double y; }; int kase; point points[105];//记录最开始的多边形 point q[105]; //临时保存新切割的多边形 point p[105]; //保存新切割出的多边形 int n,ccnt;//n的原先的点数,m是新切割出的多边形的点数 double a,b,c; void getline(point x, point y) //获取直线ax+by+c==0 { a = y.y - x.y; b = x.x - y.x; c = y.x * x.y- x.x * y.y; } point get_intersection(point x,point y) //获取直线ax+by+c==0 和点x和y所连直线的交点 { double u = fabs(a * x.x + b * x.y + c); double v = fabs(a * y.x + b * y.y + c); point ans; ans.x = (x.x * v + y.x * u) / (u + v); ans.y = (x.y * v + y.y * u) / (u + v); return ans; } void cut() //用直线ax+by+c==0切割多边形 { int tot=0,i; for(i = 1;i <= ccnt; i++) { if(a * p[i].x + b * p[i].y + c >= 0) //题目是顺时钟给出点的 { //所以一个点在直线右边的话,那么带入值就会大于等于0, 注意直线是有方向的,这个用向量比较好理解 q[++tot] = p[i]; //说明这个点还在切割后的多边形内,将其保留 } else { if(a * p[i-1].x + b * p[i-1].y + c > 0) //该点不在多边形内,但是它和它相邻的点构成直线与 { //ax+by+c==0所构成的交点可能在新切割出的多边形内, q[++tot] = get_intersection(p[i-1], p[i]); //所以保留交点 } if(a * p[i+1].x + b * p[i+1].y + c > 0) { q[++tot] = get_intersection(p[i+1], p[i]); } } } for(i = 1; i <= tot; i++) { p[i] = q[i]; } p[tot+1] = q[1]; p[0] = q[tot]; ccnt = tot; } void solve() { int i; for(i = 1; i <= n; i++) { p[i] = points[i]; } points[n+1] = points[1]; p[n+1] = p[1]; p[0] = p[n]; ccnt = n; for(i = 1;i <= n; i++) { getline(points[i], points[i+1]); //根据point[i]和point[i+1]确定直线ax+by+c==0 cut(); //用直线ax+by+c==0切割多边形 } if(ccnt == 0) { printf("Floor #%d\nSurveillance is impossible.\n\n", ++kase); } else { printf("Floor #%d\nSurveillance is possible.\n\n", ++kase); } } int main() { int cas,i; while (~scanf("%d", &n) && n) { for(i = 1;i <= n; i++) { scanf("%lf %lf", &points[i].x, &points[i].y); } solve(); } return 0; }