=====0.声明===== **1.点的存储** 点的保存为直角坐标系,如下 class Point{ public: ld x , y; //ld = long double }; **2.边的存储** 边的保存为两个点$(p , v)$,其中$p$为直线上某一点,$v$为方向向量,$angle$是直线的倾角(用于排序) class Line{ public: Point p , v; ld angle; }; **3.函数使用** 求两直线交点(调用前需保证两直线不能平行) Point Linewithline(Line a , Line b){ Point u = a.p - b.p; ld t = (b.v * u) / (a.v * b.v); return a.p + a.v * t; } 判断一个点是否在某一条直线对应的半平面内(严格内部) bool Bothside(Point p , Line a){ return a.v * (p - a.p) > eps; } =====1.引入===== 在一个无限大的平面上,有$n$条直线。每条直线相当于一次切割,只保留目前平面中这条直线左边或者右边的部分,求最终剩余平面。 由于每条直线的切割可以看成一个半平面,最终平面可以看成这$n$个半平面的交。 =====2.算法介绍===== **1.朴素的做法** 此做法不做为讲述重点。 我们可以存储半平面对应的所有交点和交线,对于每一个新的半平面,我们暴力的枚举每一条线,并把这条线和半平面对应的分界线求交点。 当合法的交点数恰好为2时,我们可以根据方向留下那一半的直线和点,删去另一半直线和点,加入这条直线和两个新的交点。 否则对应这个半平面我们不做任何处理。 该方法时间复杂度为$O(n^2)$ 此方法由于复杂度较高,在大部分题目中都无法使用,因此在这里不提供代码了。 **2.优化的做法** 在朴素的做法中,我们对于最终的半平面交是一个凸包的这个性质并没有使用。如果我们有效的利用这个性质,将能得到时间复杂度更低的做法。 首先我们按照半平面的分界线的方向角度$angle$排序,对于方向角相同的半平面,我们留那个半平面面积更小的直线(即所留的直线在另外一条直线的内部)。 我们尝试去维护一个下凸壳(这个下凸壳包围的区域就是目前得到的半平面)。 对于一个新加的半平面(对应直线$L_i$),我们尝试将目前下凸壳的点$P_{tail}$(下凸壳的点是相邻两组成直线的交点)和直线$L_i$进行比较,$P_{tail}$如果不在$L_i$一侧,就说明该交点和下凸壳最后一条直线已经没有任何的意义(这条线严格在最终交平面外面),将这条线删除,并继续和前一交点$P_{tail-1}$比较,直到得到下凸壳中无点或者点严格在$L_i$一侧。同时在下凸壳靠前的点,我们也通过$L_i$往后筛,如果点$P_{head}$不符合要求(和前面一样),删除并继续比较,直到下凸壳中无点或者点严格在$L_i$一侧。 在过程中要同时维护下凸壳的点集合$P$和边集合$Q$。 最后再用下凸壳中的第一条边,从后往前删除不符合要求的点。 最终$P$和$Q$即为所求 该方法时间复杂度为$O(nlogn)$ 代码 void HalfPlaneIntersect(Line *L){ int head = 0 , tail = 0; Q[head] = L[1]; for(int i = 2 ; i <= n ; ++ i){ if(fabs(L[i].angle - L[i - 1].angle) < eps) continue; //两条直线平行 while(head < tail && !Bothside(P[tail - 1] , L[i])) tail --; while(head < tail && !Bothside(P[head] , L[i])) head ++; Q[++ tail] = L[i]; if(head < tail) P[tail - 1] = Linewithline(Q[tail] , Q[tail - 1]); } while(head < tail && !Bothside(P[tail - 1] , Q[head])) tail --; P[tail] = Linewithline(Q[tail] , Q[head]); } **3.应用** **4.未来扩展** 可以研究如何利用set进行动态半平面交。