半平面的核

【题意】给出很多个半平面,这里每个半平面由线段组成,都是指向线段方向的左边表示有
(x1 - x) * (y2 - y) - (x2 - x) * (y1 - y) >=0 ( >=0 表示左边,<=0 表示右边)
要你求个半平面的核,就是所有半平面所围成的面积
【算法】O(nlogn)的半平面交算法,最后统计出得到的多边形的点,然后利用叉积公式求出面积就行了

#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
const double eps=1e-10,big=10000.0;
const int maxn = 20010;
struct point
{
    double x,y;
};
struct polygon   //存放最后半平面交中相邻边的交点,就是一个多边形的所有点
{
    int n;
    point p[maxn];
};
struct line   //半平面,这里是线段
{
    point a,b;
};
double at2[maxn];
int ord[maxn],dq[maxn+1],lnum;

int n;
polygon pg;

line ls[maxn]; //半平面集合
inline int sig(double k)   //判是不是等于0,返回-101,分别是小于,等于,大于
{
    return (k < -eps)? -1: k > eps;
}
//叉积>0代表在左边,<0代表在右边,=0代表共线
//e是否在o->s的左边onleft(sig(multi))>=0
inline double multi(point o, point s, point e)
{
    //构造向量,然后返回叉积
    return (s.x-o.x)*(e.y-o.y)-(e.x-o.x)*(s.y-o.y);
}
//直线求交点
point isIntersected(point s1, point e1, point s2, point e2)
{
    double dot1,dot2;
    point pp;
    dot1 = multi(s2,e1,s1);
    dot2 = multi(e1,e2,s1);
    pp.x = (s2.x * dot2 + e2.x * dot1) / (dot2 + dot1);
    pp.y = (s2.y * dot2 + e2.y * dot1) / (dot2 + dot1);
    return pp;
}
//象限排序
inline bool cmp(int u,int v)
{
    if(sig(at2[u]-at2[v])==0)
        return sig(multi(ls[v].a,ls[v].b,ls[u].b))>=0;
    return at2[u]<at2[v];
}
//判断半平面的交点在当前半平面外
bool judgein(int x,int y,int z)
{
    point pnt = isIntersected(ls[x].a, ls[x].b, ls[y].a, ls[y].b); //求交点
    return  sig(multi(ls[z].a,ls[z].b,pnt)) < 0;  
    //判断交点位置,如果在右面,返回true,如果要排除三点共线,改成<=
}
//半平面交
void HalfPlaneIntersection(polygon &pg)   //预处理
{
    int n = lnum , tmpn , i;
    /*  对于atan2(y,x)
        结果为正表示从 X 轴逆时针旋转的角度,结果为负表示从 X 轴顺时针旋转的角度。
           atan2(a, b) 与 atan(a/b)稍有不同,atan2(a,b)的取值范围介于 -pi 到 pi 之间(不包括 -pi),
           而atan(a/b)的取值范围介于-pi/2到pi/2之间(不包括±pi)*/
    for(i = 0 ; i < n ; i ++)
    {
        //atan2(y,x)求出每条线段对应坐标系的角度
        at2[i] = atan2( ls[i].b.y - ls[i].a.y, ls[i].b.x - ls[i].a.x);
        ord[i] = i;
    }
    sort(ord , ord + n , cmp);
    for (i = 1 , tmpn = 1 ; i < n ; i++) //处理重线的情况
        if( sig(at2[ord[i-1]] - at2[ord[i]]) != 0 ) ord[tmpn++] = ord[i];
    n = tmpn;
    //圈地
    int bot = 1,top = bot + 1; //双端栈,bot为栈底,top为栈顶
    dq[bot] = ord[0];
    dq[top] = ord[1]; //先压两根线进栈
    for(i = 2 ; i < n ; i ++)
    {
        //bot < top 表示要保证栈里至少有2条线段,如果剩下1条,就不继续退栈
        //judgein,判断如果栈中两条线的交点如果在当前插入先的右边,就退栈
        while( bot < top && judgein(dq[top-1] , dq[top] , ord[i]) ) top--;
        //对栈顶要同样的操作
        while( bot < top && judgein(dq[bot+1] , dq[bot] , ord[i]) ) bot++;
        dq[++top] = ord[i];
    }
    //最后还要处理一下栈里面存在的栈顶的线在栈底交点末尾位置,或者栈顶在栈尾两条线的右边
    while( bot < top && judgein(dq[top-1] , dq[top] , dq[bot]) ) top--;
    while( bot < top && judgein(dq[bot+1] , dq[bot] , dq[top]) ) bot++;
    //最后一条线是重合的
    dq[--bot] = dq[top];
    //求多边形
    pg.n = 0;
    for(i = bot + 1; i <= top ; i++) //求相邻两条线的交点
        pg.p[pg.n++] = isIntersected(ls[dq[i-1]].a, ls[dq[i-1]].b, ls[dq[i]].a,ls[dq[i]].b);
}

inline void add(double a,double b,double c,double d)
{
    //添加线段
    ls[lnum].a.x = a;
    ls[lnum].a.y = b;
    ls[lnum].b.x = c;
    ls[lnum].b.y = d;
    lnum++;
}
int main()
{
    int n,i;
    scanf("%d",&n);
    double a,b,c,d;
    for(i = 0 ; i < n ; i ++)
    {
        //输入代表一条向量(x = (c - a),y = (d - b));
        scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
        add(a,b,c,d);
    }
    //下面是构造一个大矩形边界
    add(0,0,big,0);//down
    add(big,0,big,big);//right
    add(big,big,0,big);//up
    add(0,big,0,0);//left
    HalfPlaneIntersection(pg); //求半平面交
    double area = 0;
    n = pg.n;
    ///最后多边形的各个点保存在pg里面
    for(i = 0 ; i < n ; i ++)
        area += pg.p[i].x * pg.p[(i+1)%n].y - pg.p[(i+1)%n].x * pg.p[i].y;
        //x1 * y2 - x2 * y1用叉积求多边形面积
    area=fabs(area)/2.0; 
    //所有面积应该是三角形面积之和,而叉积求出来的是四边形的面积和,所以要除2

    printf("%.1f\n",area);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值