#include <bits/stdc++.h>
// from: cpp.json
#define INF 8e18
#define int long long
using namespace std;
// 几何模板
template<class T>
struct Point {
T x;
T y;
Point(const T &x_ = 0, const T &y_ = 0) : x(x_), y(y_) {}
template<class U>
operator Point<U>() {
return Point<U>(U(x), U(y));
}
Point &operator+=(const Point &p) & {
x += p.x;
y += p.y;
return *this;
}
Point &operator-=(const Point &p) & {
x -= p.x;
y -= p.y;
return *this;
}
Point &operator*=(const T &v) & {
x *= v;
y *= v;
return *this;
}
Point &operator/=(const T &v) & {
x /= v;
y /= v;
return *this;
}
Point operator-() const {
return Point(-x, -y);
}
friend Point operator+(Point a, const Point &b) {
return a += b;
}
friend Point operator-(Point a, const Point &b) {
return a -= b;
}
friend Point operator*(Point a, const T &b) {
return a *= b;
}
friend Point operator/(Point a, const T &b) {
return a /= b;
}
friend Point operator*(const T &a, Point b) {
return b *= a;
}
friend bool operator<(Point a, Point b) {
return a.x == b.x ? a.y < b.y : a.x < b.x;
}
friend bool operator==(const Point &a, const Point &b) {
return a.x == b.x && a.y == b.y;
}
friend std::istream &operator>>(std::istream &is, Point &p) {
return is >> p.x >> p.y;
}
friend std::ostream &operator<<(std::ostream &os, const Point &p) {
return os << "(" << p.x << ", " << p.y << ")";
}
};
template<class T>
struct Line {
Point<T> a;
Point<T> b;
Line(const Point<T> &a_ = Point<T>(), const Point<T> &b_ = Point<T>()) : a(a_), b(b_) {}
};
/*
计算向量 a 和 b 的点积(内积)
dot(a, b) = a.x*b.x + a.y*b.y
*/
template<class T>
T dot(const Point<T> &a, const Point<T> &b) {
return a.x * b.x + a.y * b.y;
}
template<class T>
T dot3(const Point<T> &a, const Point<T> &b, const Point<T> &c) {
return dot(b - a, c - a);
}
/*
计算向量 a 和 b 的叉积(2D 向量的“伪叉积”)
cross(a, b) = a.x*b.y - a.y*b.x
正值表示 b 在 a 的逆时针方向,负值表示顺时针方向
*/
template<class T>
T cross(const Point<T> &a, const Point<T> &b) {
return a.x * b.y - a.y * b.x;
}
template<class T>
T cross3(const Point<T> &a, const Point<T> &b, const Point<T> &c) {
return cross(b - a, c - a);
}
/*
计算点 p 的平方模:|p|^2 = dot(p, p)
*/
template<class T>
T square(const Point<T> &p) {
return dot(p, p);
}
template<class T>
T dist2(Point<T> &A, Point<T> &B) {
T dx = A.x - B.x;
T dy = A.y - B.y;
return dx * dx - dy * dy;
}
/*
计算点 p 的长度(欧几里得范数),返回 double
*/
template<class T>
long double length(const Point<T> &p) {
return sqrtl(square(p));
}
/*
计算线段 l 的长度
通过端点之差的长度来计算
*/
template<class T>
long double length(const Line<T> &l) {
return length(l.a - l.b);
}
/*
将向量 p 归一化:返回单位向量 p/|p|
注意:调用者需保证 p != (0,0)
*/
template<class T>
Point<T> normalize(const Point<T> &p) {
return p / length(p);
}
/*
判断两条直线(线段) l1, l2 是否平行
平行当且仅当方向向量叉积为 0
*/
template<class T>
bool parallel(const Line<T> &l1, const Line<T> &l2) {
return cross(l1.b - l1.a, l2.b - l2.a) == 0;
}
/*
计算两点 a, b 之间的距离,等价于 |a - b|
*/
template<class T>
long double distance(const Point<T> &a, const Point<T> &b) {
return length(a - b);
}
/*
计算点 p 到直线 l(通过 l.a 和 l.b 定义)的垂线距离
distancePL = |cross(l.a-l.b, l.a-p)| / |l|
*/
template<class T>
long double distancePL(const Point<T> &p, const Line<T> &l) {
return std::abs(cross(l.a - l.b, l.a - p)) / length(l);
}
/*
计算点 p 到线段 l 的最短距离
如果垂足在线段内部,返回垂线距离;否则返回到最近端点的距离
*/
template<class T>
long double distancePS(const Point<T> &p, const Line<T> &l) {
// 如果 p 在 l.a 的“后方”,距离为 p 到 l.a
if (dot(p - l.a, l.b - l.a) < 0) {
return distance(p, l.a);
}
// 如果 p 在 l.b 的“后方”,距离为 p 到 l.b
if (dot(p - l.b, l.a - l.b) < 0) {
return distance(p, l.b);
}
// 否则垂足在线段内部,使用垂线距离
return distancePL(p, l);
}
/*
将点 (x, y) 绕原点逆时针旋转 90 度,得到 (-y, x)
*/
template<class T>
Point<T> rotate(const Point<T> &a) {
return Point(-a.y, a.x);
}
/*
对向量 a 做象限分类:
- 如果 a.y > 0,返回 1
- 如果 a.y == 0 且 a.x > 0,返回 1
- 否则返回 -1
用于 half-plane 排序时,将向量分到“上半平面”或“下半平面”
*/
template<class T>
int sgn(const Point<T> &a) {
return a.y > 0 || (a.y == 0 && a.x > 0) ? 1 : -1;
}
/*
判断点 p 是否在直线(有向) l 的左侧
cross(l.b - l.a, p - l.a) > 0 则 p 在 l 左侧
*/
template<class T>
bool pointOnLineLeft(const Point<T> &p, const Line<T> &l) {
return cross(l.b - l.a, p - l.a) > 0;
}
/*
将一个二维平面上的点(向量)绕原点逆时针旋转一个角度 t(单位:弧度)
*/
template<class T>
Point<T> rotateT(Point<T> a, long double t) {
long double c = cos(t);
long double s = sin(t);
return (Point<T>) {a.x * c - a.y * s, a.x * s + a.y * c};
}
/*
计算两条直线(或延长线) l1 和 l2 的交点
参数:
l1: 直线1,通过 l1.a, l1.b
l2: 直线2,通过 l2.a, l2.b
返回值:
交点坐标 = l1.a + (l1.b - l1.a) * (cross(l2.b - l2.a, l1.a - l2.a) / cross(l2.b - l2.a, l1.b - l1.a))
需要保证两直线不平行,否则除以 0
*/
template<class T>
Point<T> lineIntersection(const Line<T>& L1, const Line<T>& L2) {
// d1 = L1.b - L1.a, d2 = L2.b - L2.a
Point<T> d1 = L1.b - L1.a;
Point<T> d2 = L2.b - L2.a;
// 分母:d1 × d2
T den = cross(d1, d2);
// 分子:(L2.a - L1.a) × d2
T num = cross(L2.a - L1.a, d2);
// 题目保证 den != 0,所以直接用
T t = num / den;
return L1.a + d1 * t;
}
/*
判断点 p 是否在线段 l 上(闭区间)
条件:
1) p 与 l.a, l.b 共线:cross(p - l.a, l.b - l.a) == 0
2) p.x 在 [min(l.a.x, l.b.x), max(l.a.x, l.b.x)] 范围内
3) p.y 在 [min(l.a.y, l.b.y), max(l.a.y, l.b.y)] 范围内
*/
template<class T>
bool pointOnSegment(const Point<T> &p, const Line<T> &l) {
return cross(p - l.a, l.b - l.a) == 0
&& std::min(l.a.x, l.b.x) <= p.x && p.x <= std::max(l.a.x, l.b.x)
&& std::min(l.a.y, l.b.y) <= p.y && p.y <= std::max(l.a.y, l.b.y);
}
/*
判断点 a 是否在简单多边形 p 内部或边界上
使用射线法(or 奇偶性算法):
1) 若 a 在任意一个边的线段上,则视为在多边形内,直接返回 true
2) 否则,对从 a 向 +X 方向发射一条水平射线,统计与多边形边的交点个数
- 若交点数为奇数,则在内部;为偶数,则在外部
注意:为了避免射线与顶点共线带来的歧义,使用如下两种情况分别异或计数
*/
template<class T>
bool pointInPolygon(const Point<T> &a, const std::vector<Point<T>> &p) {
int n = p.size();
// 先检查是否在任意边线上
for (int i = 0; i < n; i++) {
if (pointOnSegment(a, Line(p[i], p[(i + 1) % n]))) {
return true;
}
}
// 射线交点计数
int t = 0;
for (int i = 0; i < n; i++) {
auto u = p[i];
auto v = p[(i + 1) % n];
// 如果边 (u->v) 从左下方穿过射线
if (u.x < a.x && v.x >= a.x && pointOnLineLeft(a, Line(v, u))) {
t ^= 1; // 交点数 +1 (异或翻转)
}
// 如果边 (u->v) 从右下方穿过射线
if (u.x >= a.x && v.x < a.x && pointOnLineLeft(a, Line(u, v))) {
t ^= 1; // 交点数 +1 (异或翻转)
}
}
return t == 1;
}
/*
计算两条线段 l1, l2 的相交情况
返回值:tuple{type, p1, p2}
type = 0: 不相交
type = 1: 普通相交(在内部相交),交点为 p1==p2
type = 2: 重叠(共线且有重叠区间),重叠区间端点为 p1, p2
type = 3: 在端点相交(恰好在某个端点相交),交点为 p1==p2
具体步骤:
1) 先用包围盒快速排除:如果两个包围盒不重叠,返回 0
2) 计算两条线段方向向量叉积,若为 0,则可能共线或平行
- 如果不共线,返回 0
- 如果共线,则按区间重叠来判断是 2 还是 3
3) 非共线时,计算四个叉积 (cp1, cp2, cp3, cp4),判断是否跨立
- 若不跨立,返回 0
- 若跨立,计算交点 p = lineIntersection(...)
- 若四个叉积都不为 0,返回 type=1
- 否则必然是类型 3,在端点相交
*/
template<class T>
tuple<int, Point<T>, Point<T>> segmentIntersection(const Line<T> &l1, const Line<T> &l2) {
// 包围盒检查:如果 l1 最大 x 坐标 < l2 最小 x,则不可能相交
if (std::max(l1.a.x, l1.b.x) < std::min(l2.a.x, l2.b.x)) {
return {0, Point<T>(), Point<T>()};
}
// 如果 l1 最小 x > l2 最大 x,排除
if (std::min(l1.a.x, l1.b.x) > std::max(l2.a.x, l2.b.x)) {
return {0, Point<T>(), Point<T>()};
}
// 如果 l1 最大 y < l2 最小 y,排除
if (std::max(l1.a.y, l1.b.y) < std::min(l2.a.y, l2.b.y)) {
return {0, Point<T>(), Point<T>()};
}
// 如果 l1 最小 y > l2 最大 y,排除
if (std::min(l1.a.y, l1.b.y) > std::max(l2.a.y, l2.b.y)) {
return {0, Point<T>(), Point<T>()};
}
// 计算方向向量叉积
if (cross(l1.b - l1.a, l2.b - l2.a) == 0) {
// 如果在同一直线上(共线)
if (cross(l1.b - l1.a, l2.a - l1.a) != 0) {
// 平行但不共线
return {0, Point<T>(), Point<T>()};
} else {
// 共线:找出重叠的区间端点
auto maxx1 = std::max(l1.a.x, l1.b.x);
auto minx1 = std::min(l1.a.x, l1.b.x);
auto maxy1 = std::max(l1.a.y, l1.b.y);
auto miny1 = std::min(l1.a.y, l1.b.y);
auto maxx2 = std::max(l2.a.x, l2.b.x);
auto minx2 = std::min(l2.a.x, l2.b.x);
auto maxy2 = std::max(l2.a.y, l2.b.y);
auto miny2 = std::min(l2.a.y, l2.b.y);
// 重叠区间的左下端点
Point<T> p1(std::max(minx1, minx2), std::max(miny1, miny2));
// 重叠区间的右上端点
Point<T> p2(std::min(maxx1, maxx2), std::min(maxy1, maxy2));
// 如果 p1 不在线段 l1 上,则将 y 坐标交换,使得真正的重叠端点为 p2
if (!pointOnSegment(p1, l1)) {
std::swap(p1.y, p2.y);
}
// 如果 p1 == p2,则只有一个公共端点,返回 type=3
if (p1 == p2) {
return {3, p1, p2};
} else {
// 否则返回重叠区间 (p1, p2),type = 2
return {2, p1, p2};
}
}
}
// 非共线:计算四个叉积,判断跨立
auto cp1 = cross(l2.a - l1.a, l2.b - l1.a);
auto cp2 = cross(l2.a - l1.b, l2.b - l1.b);
auto cp3 = cross(l1.a - l2.a, l1.b - l2.a);
auto cp4 = cross(l1.a - l2.b, l1.b - l2.b);
// 如果不跨立,则不相交
if ((cp1 > 0 && cp2 > 0) || (cp1 < 0 && cp2 < 0) ||
(cp3 > 0 && cp4 > 0) || (cp3 < 0 && cp4 < 0)) {
return {0, Point<T>(), Point<T>()};
}
// 计算交点
Point<T> p = lineIntersection(l1, l2);
// 如果四个叉积都不为 0,则是普通相交
if (cp1 != 0 && cp2 != 0 && cp3 != 0 && cp4 != 0) {
return {1, p, p};
} else {
// 否则是端点相交
return {3, p, p};
}
}
/*
计算两条线段 l1 和 l2 之间的最短距离
如果相交则距离为 0;否则取各自端点到对方线段的最短距离中的最小值
*/
template<class T>
long double distanceSS(const Line<T> &l1, const Line<T> &l2) {
if (std::get<0>(segmentIntersection(l1, l2)) != 0) {
// 有任何形式的相交
return 0.0;
}
// 不相交时,计算 4 种端点-线段距离,取最小值
return std::min({
distancePS(l1.a, l2),
distancePS(l1.b, l2),
distancePS(l2.a, l1),
distancePS(l2.b, l1)
});
}
/*
判断线段 l 是否完全在简单多边形 p 内部(含边界)
逻辑:
1) 先判断 l 的两端点是否在多边形内部或边界上,若有一个不在,则返回 false
2) 对多边形的每条边做相交检测:
- 如果类型为 1(严格相交),则 l 与该边在内部相交,返回 false
- 如果类型为 2(重叠),需要进一步判断重叠部分是否在合法范围内:
如果重叠的顶点 v 既不等于 l.a 且不等于 l.b,并且与该顶点相关的内角为凸角,则返回 false
- 如果类型为 3(端点相交),需判断交点是否在合法位置,否则返回 false
3) 如果遍历完所有边仍未返回 false,则说明线段在多边形内
*/
template<class T>
bool segmentInPolygon(const Line<T> &l, const std::vector<Point<T>> &p) {
int n = p.size();
// 1) 两个端点必须都在多边形内部或边界上
if (!pointInPolygon(l.a, p)) {
return false;
}
if (!pointInPolygon(l.b, p)) {
return false;
}
// 2) 遍历多边形的每一条边
for (int i = 0; i < n; i++) {
auto u = p[i];
auto v = p[(i + 1) % n];
auto w = p[(i + 2) % n];
// 计算 l 与多边形边 (u->v) 的相交情况
auto [t, p1, p2] = segmentIntersection(l, Line(u, v));
if (t == 1) {
// 严格相交(在边的内部相交),线段不在多边形内
return false;
}
if (t == 0) {
// 不相交,检验下一条边
continue;
}
if (t == 2) {
// 重叠:重叠的区间端点是 p1, p2
// 如果重叠的顶点 v 既不等于 l.a 且不等于 l.b
// 并且在该顶点处的内角是凸角 (cross(v-u, w-v) > 0),则线段越界
if (pointOnSegment(v, l) && v != l.a && v != l.b) {
if (cross(v - u, w - v) > 0) {
return false;
}
}
} else {
// t == 3:端点相交
// 如果交点 p1 既不等于 u 也不等于 v,则说明交在边的内部
if (p1 != u && p1 != v) {
// 如果 l 的端点在该边的“左侧”,说明 l 部分在外部
if (pointOnLineLeft(l.a, Line(v, u)) || pointOnLineLeft(l.b, Line(v, u))) {
return false;
}
} else if (p1 == v) {
// 交点在顶点 v 处,需要判断与 v 相邻的两条边 (u-v) 和 (v-w) 的位置关系
if (l.a == v) {
// 交点在 l.a
if (pointOnLineLeft(u, l)) {
// u 在 l 的左侧
if (pointOnLineLeft(w, l) && pointOnLineLeft(w, Line(u, v))) {
return false;
}
} else {
// u 在 l 的右侧或共线
if (pointOnLineLeft(w, l) || pointOnLineLeft(w, Line(u, v))) {
return false;
}
}
} else if (l.b == v) {
// 交点在 l.b
if (pointOnLineLeft(u, Line(l.b, l.a))) {
// u 相对于(l.b->l.a)的左侧
if (pointOnLineLeft(w, Line(l.b, l.a)) && pointOnLineLeft(w, Line(u, v))) {
return false;
}
} else {
// u 相对于(l.b->l.a)的右侧或共线
if (pointOnLineLeft(w, Line(l.b, l.a)) || pointOnLineLeft(w, Line(u, v))) {
return false;
}
}
} else {
// 交点在 v,但既不等于 l.a 也不等于 l.b:
// 此时 l 穿过 v,判断 u 和 w 两边是否进入多边形外部
if (pointOnLineLeft(u, l)) {
if (pointOnLineLeft(w, Line(l.b, l.a)) || pointOnLineLeft(w, Line(u, v))) {
return false;
}
} else {
if (pointOnLineLeft(w, l) || pointOnLineLeft(w, Line(u, v))) {
return false;
}
}
}
}
}
}
// 如果所有边都检查完毕,没有发现越界情况,则线段在多边形内
return true;
}
/*
half-plane intersection(半平面交,求凸多边形的顶点序列)
输入:一组有向直线(通常是边界线,每条线的左侧是合法区域)
输出:凸多边形的顶点顺序(逆时针或顺时针,依实现而定)
算法思路(常见的双端队列实现):
1) 按极角排序:先比较方向向量落在哪个半平面,再比较叉积确定顺序
2) 枚举排序后每条直线,用一个双端队列维护当前半平面交的边集合
3) 每加一条线时,从队首和队尾弹出不满足“左侧”条件的交点
4) 最后队列剩余的边会形成一个稳态的凸多边形
*/
template<class T>
std::vector<Point<T>> hp(std::vector<Line<T>> lines) {
// 1) 先按照极角排序
std::sort(lines.begin(), lines.end(), [&](auto l1, auto l2) {
// 计算方向向量 d1, d2
auto d1 = l1.b - l1.a;
auto d2 = l2.b - l2.a;
// 如果一个在上半平面,另一个在下半平面,上半平面排前
if (sgn(d1) != sgn(d2)) {
return sgn(d1) == 1;
}
// 否则按 d1 vs d2 的叉积大者排前 (逆时针排序)
return cross(d1, d2) > 0;
});
deque<Line<T>> ls; // 存储当前半平面交的边
deque<Point<T>> ps; // 存储对应的交点
for (auto l : lines) {
if (ls.empty()) {
// 队列为空时直接加入
ls.push_back(l);
continue;
}
// 从尾部弹出:如果最后一个交点不在新半平面的左侧,则移除
while (!ps.empty() && !pointOnLineLeft(ps.back(), l)) {
ps.pop_back();
ls.pop_back();
}
// 从头部弹出:如果最前一个交点不在新半平面的左侧,则移除
while (!ps.empty() && !pointOnLineLeft(ps.front(), l)) {
ps.pop_front();
ls.pop_front();
}
// 如果新直线和平行队尾直线同向,则只保留一条
if (!ls.empty() && cross(l.b - l.a, ls.back().b - ls.back().a) == 0) {
// 同向:保留方向更“内”的那条
if (dot(l.b - l.a, ls.back().b - ls.back().a) > 0) {
// 如果队尾直线的 a 不在当前新半平面左侧,则替换
if (!pointOnLineLeft(ls.back().a, l)) {
assert(ls.size() == 1); // 确保队列中只有这一条线
ls[0] = l;
}
continue;
}
// 反向:说明无解
return {};
}
// 计算新线与队尾直线的交点,加入交点队列
ps.push_back(lineIntersection(ls.back(), l));
ls.push_back(l);
}
// 最后收尾:去除不满足条件的交点
while (!ps.empty() && !pointOnLineLeft(ps.back(), ls[0])) {
ps.pop_back();
ls.pop_back();
}
// 如果有效直线少于等于 2,则无解或退化
if (ls.size() <= 2) {
return {};
}
// 加入首尾两条直线的交点,完成多边形闭合
ps.push_back(lineIntersection(ls[0], ls.back()));
// 返回最终的交点序列,即凸多边形的顶点列表
return vector<Point<T>>(ps.begin(), ps.end());
}
using LD = long double;
using P = Point<LD>;
const LD PI = acos(-1);
constexpr LD eps = 0;
void solve() {
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
// cin >> t;
while(t--){
solve();
}
return 0;
}