P4196 [CQOI2006]凸多边形 /【模板】半平面交 题解

题目传送门:洛谷

简要题意

逆时针给出 \(n\) 个二维多边形的顶点坐标,求他们交的面积结果保留三位小数。

\(2 \le n \le 10,3 \le m_i \le 50\)\(m_i\) 表示第 \(i\) 个多边形的边数。保证每维坐标为 \([-1000,1000]\) 中的整数。

解题思路

下设 \(a=\sum\limits_{i =1}\limits^nm_i\)

半平面交模板题,对于这一题可以考虑暴力 \(\mathcal{O}(a^2)\) 求解。但是这样并不能通过大部分的半平面交题目。

我们需要考虑更优的做法。有两种:分治、双端队列。它们的时间复杂度相同,但是分治的常数以及实现难度都高于双端队列做法,那我要他作甚

这两种做法的时间复杂度都是 \(\mathcal{O}(a\log_2a)\)

所以本文只涉及双端队列做法,还不是笔者太弱不会分治做法

双端队列求解半平面交问题

既然你来了这里笔者默认你已经学会了二维凸包,如果不会的话请出门右转二维凸包

  • 首先,不妨假设直线的左侧(如果你想,右边也不是不行)是半平面,在对直线进行极角排序后我们进行“去重”,保留极角相同的直线中最靠左的(即限制最严格的)。

  • 下一步,扫描一遍所有直线,如果队首的两条直线的交点在当前直线的右侧,则从队首删除一条直线,队尾做同样的操作。接着将当前直线压入队尾。

  • 完成了上面一步可能还不行,因为队首的交点可能还会在队尾直线的右侧,所以还要删除不合法的,队尾同理(虽然可能并不需要,但保险起见)。

  • 最后我们就可以得到围出半平面交的所有直线,计算交点,用叉积求面积即可。

代码实现

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

const double eps = 1e-8;//避免浮点数误差
const int N = 504;
int n, cnt, tot;
double sum;

struct node {
    double x, y;
    node(double x = 0, double y = 0):
    x(x), y(y){}
    node operator - (node &b) {
        return node(x - b.x, y - b.y);
    }
} nod[N], ans[N];

double cpr(node a, node b) {
    return (a.x * b.y - a.y * b.x);//两点的叉积
}
double cpr(node a, node b, node c) {
    return cpr(b - a, c - a);
}

struct edge {
    node start, end;
    double angle;//极角
    edge(node start = node(), node end = node()):
    start(start), end(end), angle(atan2((start - end).y, (start - end).x)){}
    bool operator < (const edge &b) const {//极角排序的比较
        if(fabs(angle - b.angle) <= eps) return cpr(start, b.start, b.end) > 0;
        return angle < b.angle;//极角相同的优先把左边的放在前面(否则去重的时候会出现一些“小问题”)
    }
} e[N], deq[N];

node getnode(edge a, edge b){//两直线的计算交点
    double s1 = cpr(a.start, b.end, a.end);
    double s2 = cpr(a.start, b.start, a.end);
    return node((s1 * b.start.x - s2 * b.end.x) / (s1 - s2), (s1 * b.start.y - s2 * b.end.y) / (s1 - s2));
}
bool check(edge a, edge b, edge c){
    node p = getnode(b, c);
    return cpr(p, a.start, a.end) < 0;//判断b,c直线的交点是否在a直线的右侧
}
void HalfPlane() {//半平面交
    sort(e + 1, e + cnt + 1);
    tot = 1;
    for(int i = 2; i <= cnt; i++)
        if(fabs(e[i].angle - e[i - 1].angle) > eps)
            e[++tot] = e[i];
    int top = 2, back = 1;
    deq[1] = e[1];
    deq[2] = e[2];
    for(int i = 3; i <= tot; i++){
        while(back < top && check(e[i], deq[top], deq[top - 1])) top--;
        while(back < top && check(e[i], deq[back], deq[back + 1])) back++;
        deq[++top] = e[i];
    }
    while(back < top && check(deq[back], deq[top], deq[top - 1])) top--;
    while(back < top && check(deq[top], deq[back], deq[back + 1])) back++;
    for(int i = back; i < top; i++)
        ans[i - back + 1] = getnode(deq[i], deq[i + 1]);
    if(top - back + 1 > 2)//可能会出现半平面交的面积为0的情况,所以需要判断是否有2条以上的直线围出了半平面交
        ans[top - back + 1] = getnode(deq[top], deq[back]);
    tot = top - back + 1;
}

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        int m; 
        scanf("%d", &m);
        for(int j = 1; j <= m; j++)
            scanf("%lf%lf", &nod[j].x, &nod[j].y);
        for(int j = 1; j <= m; j++)
            e[++cnt] = edge(nod[j], nod[j % m + 1]);
    }
    HalfPlane();
    for(int i = 1; i <= tot; i++)
        sum += cpr(ans[i], ans[i % tot + 1]);//通过叉积计算面积
    printf("%.3lf", fabs(sum) / 2);
}
/*
*/
posted @ 2023-01-12 11:15  JR_ytxy  阅读(66)  评论(0)    收藏  举报