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);
}
/*
*/

浙公网安备 33010602011771号