【BZOJ2618】【Cqoi2006】凸多边形 半平面交 、算法的深度细节剖析。
题解:虽然这道题数据范围太小,O(n*n)的算法都能过,但是我为了练手仍写了半平面交。。
半平面交:
我们规定:一个基准点+一个向量(本质是有向直线,)就算一个半平面,现在要求出半平面交,然后做一些事情。。
那么可以过每个半平面的基准点做一条平行于x轴的向右的射线,此射线与向量代表直线的夹角为其“极角”。。
我们可以把所有半平面(Line,或者说叫有向直线)以其极角为键值排序,
然后扫一圈,围出来一个图形,即要求的半平面交。。
实现过程不妨看代码,有详细注释。
代码中有几个需要画图的地方,图将会被附在代码后面~
先发个模板:
struct Point { double x,y; Point(double _x,double _y):x(_x),y(_y){} Point(){} void read(){scanf("%lf%lf",&x,&y);} Point operator + (const Point &a)const{return Point(x+a.x,y+a.y);} Point operator - (const Point &a)const{return Point(a.x-x,a.y-y);} Point operator * (const double a)const{return Point(x*a,y*a);} }P[N]; struct Line { Point u,v; double Ang; Line(Point _u,Point _v):u(_u),v(_v){Ang=atan2(v.y,v.x);} Line(){} bool operator < (const Line &a)const{return Ang<a.Ang;} }L[N]; double xmul(Point a,Point b){return a.x*b.y-a.y*b.x;} inline bool onleft(Point a,Line A){return xmul(A.v,A.u-a)>eps;} Point Line_intersection(Line a,Line b) { Point u=a.u-b.u; double t=xmul(u,b.v)/xmul(a.v,b.v); return a.u+a.v*t; } int half_planes_intersection(int size) { sort(L+1,L+size+1); int i,l,r; Line q[N]; q[l=r=1]=L[1]; Point p[N]; for(i=2;i<=size;i++) { while(l<r&&!onleft(p[r-1],L[i]))r--; while(l<r&&!onleft(p[l],L[i]))l++; q[++r]=L[i]; if(fabs(xmul(q[r].v,q[r-1].v))<eps) { r--; if(onleft(L[i].u,q[r]))q[r]=L[i]; } if(l<r)p[r-1]=Line_intersection(q[r-1],q[r]); } while(l<r&&!onleft(p[r-1],q[l]))r--; if(r-l<=1)return 0; p[r]=Line_intersection(q[l],q[r]); int m=0; for(i=l;i<=r;i++)P[++m]=p[i]; return m; }
再发代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 2000 #define eps 1e-10 using namespace std; int cnt; struct Point { double x,y; Point(double _x,double _y):x(_x),y(_y){} Point(){} void read(){scanf("%lf%lf",&x,&y);} // 重载向量加、两点之间向量、向量乘 Point operator + (const Point &a)const{return Point(x+a.x,y+a.y);} Point operator - (const Point &a)const{return Point(a.x-x,a.y-y);} Point operator * (const double a)const{return Point(x*a,y*a);} }P[N];// P:记录凸多边形上的点 struct Line { Point u,v;// u表示基准点,v是向量 double Ang;// 极角 Line(Point _u,Point _v):u(_u),v(_v){Ang=atan2(v.y,v.x);} Line(){} // 按极角排序、两直线平行的情况于半平面交函数中特判丶 bool operator < (const Line &a)const{return Ang<a.Ang;} }L[N];// 记录所有的边、取线左侧为所需半平面、、 double xmul(Point a,Point b){return a.x*b.y-a.y*b.x;}// 叉积 inline bool onleft(Point a,Line A){return xmul(A.v,A.u-a)>eps;} //判断点a是否在直线A的左侧、1左0右 Point Line_intersection(Line a,Line b)//两直线交点、 { Point u=a.u-b.u; double t=xmul(u,b.v)/xmul(a.v,b.v); return a.u+a.v*t;//向量长度是原来的t倍、 } int half_planes_intersection(int size) { sort(L+1,L+size+1); // 按照极角排一下序、 int i,l,r; Line q[N]; q[l=r=1]=L[1]; Point p[N]; for(i=2;i<=size;i++) { //新的半平面切得比较多, //甚至废掉了之前的某些半平面, //就需要出队一些元素 //这三个while的具体删除部分见博客鼠绘 while(l<r&&!onleft(p[r-1],L[i]))r--; while(l<r&&!onleft(p[l],L[i]))l++; q[++r]=L[i];//因为排过序,所以无论如何先把半平面加进去 if(fabs(xmul(q[r].v,q[r-1].v))<eps) { //如果两个直线(半平面)平行,, r--;//那就肯定有一个是废的 /**/ if(!onleft(p[r-1],L[i]))q[r]=L[i]; } if(l<r)p[r-1]=Line_intersection(q[r],q[r-1]); //求两条直线交点、即算出当前多出来的一个凸多边形上点 } while(l<r&&!onleft(p[r-1],q[l]))r--; if(l+1>=r)return 0;//半平面交把平面交没了~~ p[r]=Line_intersection(q[l],q[r]); //最后再求一次交点、、、 int m=0; for(i=l;i<=r;i++)P[++m]=p[i]; //把在凸多边形上的点copy出来,模板式,常数不太好。 return m;//返回凸多边形上点数。 } double asks(int size)//求面积,,原理我会在博客上给鼠绘。 { double ret=0; for(int i=1;i<=size;i++)ret+=xmul(P[i],P[i==size?1:i+1]); return fabs(ret/2.0); } Point po[55];//临时记录 点 (非模板内容) int main() { // freopen("test.in","r",stdin); int i,g,n; scanf("%d",&g); while(g--) {//每个凸多边形加点数个边、、 scanf("%d",&n); for(i=1;i<=n;i++)po[i].read(); L[++cnt]=Line(po[1],po[n]-po[1]); for(i=2;i<=n;i++)L[++cnt]=Line(po[i],po[i-1]-po[i]); } //函数处理。。 int num=half_planes_intersection(cnt); printf("%.3lf",asks(num)); return 0; }
学校电脑有点渣,,图片晚上回家后填坑——2014.12.04,如果我没填,请留个言提醒我谢谢。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。