计算几何之半平面交_Acwing2803

参考资料:

https://oi.wiki/geometry/half-plane/#%E7%BB%B4%E6%8A%A4%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97

半平面交的模板

  1. 将线段按照 (以:x轴的夹角为第一关键字, 如果角度相等,那么把两条线段中更靠左的排前面) 进行排序
  2. 将排好序的线段正序放入到队列中, 同时进行维护(重点!!!)
  3. 将合法的线段插入到队列后,最后用队首的向量排除一下队尾多余的向量。因为队首的向量会被后面的约束,而队尾的向量不会。此时它们围成了一个环,因此队首的向量就可以约束队尾的向量
  4. 得到由多条线段包围的半平面交123.png
  5. 求出它们的交点,然后固定一个点,旋转两个点,即可得到内部的面积之和,要注意的是area函数求出的平行四边形的面积,我们要的三角形的面积,因此答案 / 2

(1)为什么要使用:双端队列的原因:

因为后来加入的只可能会影响最开始加入的或最后加入的边(此时凸壳连通),只需要删除队首和队尾的元素,所以需要用单调队列

(2)on_right函数的判断实质

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

详细解释请看注释

#include <bits/stdc++.h>
using namespace std;

#define x first
#define y second
typedef pair<double, double> PDD;

const int N = 510;
const double eps = 1e-8;

int cnt;
struct Line //存线段
{
    PDD st, ed; //起点,终点
} line[N];
PDD pg[N], ans[N];
int q[N]; //双端队列

int sign(double x) //符号函数
{
    if (fabs(x) < eps)
        return 0;
    if (x < 0)
        return -1;
    return 1;
}

int dcmp(double x, double y) //判断相等
{
    if (fabs(x - y) < eps)
        return 0;
    if (x < y)
        return -1;
    return 1;
}

double get_angle(const Line &a) //该向量对于 x轴的角度
{
    // atan2(y, x) = 角度
    return atan2(a.ed.y - a.st.y, a.ed.x - a.st.x);
}

PDD operator-(PDD a, PDD b)
{
    return {a.x - b.x, a.y - b.y};
}

double cross(PDD a, PDD b) //叉积
{
    return a.x * b.y - a.y * b.x;
}

double area(PDD a, PDD b, PDD c) //求(ab) (ac)两个向量的平行四边形的面积
{
    return cross(b - a, c - a);
}

bool cmp(const Line &a, const Line &b) //按关于x轴的角度排序
{
    double A = get_angle(a), B = get_angle(b);
    if (!dcmp(A, B))                       //如果角度不等
        return area(a.st, a.ed, b.ed) < 0; //将更高左的排到前面
    return A < B;
}

PDD get_line_intersection(PDD p, PDD v, PDD q, PDD w) //得到两直线的交点
{
    auto u = p - q;
    double t = cross(w, u) / cross(v, w);
    return {p.x + v.x * t, p.y + v.y * t};
}

PDD get_line_intersection(Line a, Line b) //将向量转化为 点向式
{
    return get_line_intersection(a.st, a.ed - a.st, b.st, b.ed - b.st);
}

bool on_right(Line &a, Line &b, Line &c) // bc的交点是否在a的左侧
{
    auto o = get_line_intersection(b, c);
    return sign(area(a.st, a.ed, o)) <= 0;
}

double half_plane_intersection()
{
    sort(line, line + cnt, cmp); //按角度排序
    int hh = 0, tt = -1;

    for (int i = 0; i < cnt; i++)
    {
        if (i and !dcmp(get_angle(line[i]), get_angle(line[i - 1]))) //如果角度相同则跳过
            continue;
        /*
        注意事项:
        当出现一个可以把队列里的点全部弹出去的向量(即所有队列里的点都在该向量的右侧),
        则我们 必须 先处理队尾,再处理队首。因此在循环中,我们先枚举 --r; 的部分,再枚举 ++l; 的部分,才不会错。原因如下
        */
        while (hh + 1 <= tt && on_right(line[i], line[q[tt - 1]], line[q[tt]]))
            tt--;
        while (hh + 1 <= tt && on_right(line[i], line[q[hh]], line[q[hh + 1]]))
            hh++;
        q[++tt] = i;
    }

    //队尾更新队头
    while (hh + 1 <= tt && on_right(line[q[hh]], line[q[tt - 1]], line[q[tt]]))
        tt--;
    //队头更新队尾
    while (hh + 1 <= tt && on_right(line[q[tt]], line[q[hh]], line[q[hh + 1]]))
        hh++;

    q[++tt] = q[hh]; //闭合
    int k = 0;

    for (int i = hh; i < tt; i++)
        ans[k++] = get_line_intersection(line[q[i]], line[q[i + 1]]); //最后得到的多条直线的交点即是所包围的面积

    double res = 0;
    for (int i = 1; i + 1 < k; i++)
        res += area(ans[0], ans[i], ans[i + 1]);
    return res / 2; //三角剖分,但实际上area是平行四边形的面积,因此 / 2
}

int main()
{
    int n, m;
    scanf("%d", &n);
    while (n--)
    {
        scanf("%d", &m);
        for (int i = 0; i < m; i++)
            scanf("%lf%lf", &pg[i].x, &pg[i].y); //每个点的坐标
        for (int i = 0; i < m; i++)
            line[cnt++] = {pg[i], pg[(i + 1) % m]}; //存线段
    }
    double res = half_plane_intersection();
    printf("%.3lf\n", res);

    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值