1. 程式人生 > 其它 >演算法專題——計算幾何2D板子

演算法專題——計算幾何2D板子

計算幾何模板2D

一、基本操作

#define db double
const db eps = 1e-8;
const db inf = 1e20;
const db PI = acos(-1.0);
inline int sgn(db x) {return x < -eps ? -1 : x > eps;}
inline db sqr(db x) { return x*x; }
inline db mysqrt(db x) { return sqrt(max(0.0, x)); }
//注意y1給系統佔了

\(Pick\) 定理: 皮克定理是指一個計算點陣中頂點在格點上的多邊形面積公式, 該公式可以表示為 \(S=a+b/2-1\)

, 其中 \(a\) 表示多邊形內部的點數, \(b\) 表示多邊形落在格點邊界上的點數, \(S\) 表示多邊形的面積。

\(Pick\) 定理測試: POJ1265

1. 點類
/*========================================================*\
| Point 類
| 							類內函式
| Point(P x = 0, P y = 0)					- 預設建構函式
| input()									- 讀入一個點
| operator == 								- NULL
| operator <								- 用於排序
| operator +								- 返回Point
| operator -								- 返回Point
| operator *								- 返回Point
| operator /								- 返回Point
| len()										- 返回長度
| len2()									- 返回長度平方		
| trunc(db len)								- 長度變為len
| rotleft()									- 逆時針轉π/2, 返回Point
| rotright()								- 順時針轉π/2, 返回Point
| rotate(P p, db ang)						- 繞p逆時針轉ang
| 							類外函式
| *基礎功能
| Cross(P a, P b)							- NULL
| Cross(P pt, P a, P b)						- NULL
| Dot(P a, P b)								- NULL
| Dot(P pt, P a, P b)						- NULL
| Dist(P a, P b)							- NULL
| Len(a)									- 返回a的長度
| Len2(a) 									- 返回a的長度平方
| rad(P p, P a, P b) 						- 返回∠apb	
| Complex類補充
\*========================================================*/
struct Point {
    db x, y;
    Point (db x=0, db y=0): x(x), y(y) {}
    void input() { scanf("%lf%lf", &x, &y); }
    bool operator == (Point r) { return sgn(x-r.x)==0 && sgn(y-r.y)==0; }
    bool operator < (Point r) { return sgn(x-r.x)==0 ? y < r.y : x < r.x; }
    Point operator + (Point r) { return Point (x+r.x, y+r.y); }
    Point operator - (Point r) { return Point (x-r.x, y-r.y); }
    Point operator * (db r) { return Point (x*r, y*r); }
    Point operator / (db r) { return Point (x/r, y/r); }
    db len() { return mysqrt(x*x+y*y); }
    db len2() { return x*x + y*y; }
    // 化為長度為 r 的向量
    Point trunc(db len) {
        db l = mysqrt(x * x + y * y);
        if (! sgn(l)) return *this;
        len /= l;
        return Point (x*len, y*len);
    }
    // 逆時針旋轉 90 度
    Point rotleft() { return Point (-y, x); }
    // 順時針旋轉 90 度
    Point rotright() { return Point (y, -x); }
    // 繞著 p 點逆時針旋轉 ang
    Point rotate (Point p, db ang) {
        Point v = (*this) - p;
        db c = cos(ang), s = sin(ang);
        return Point (p.x + v.x*c - v.y*s, p.y + v.x*s + v.y*c);
    }
};
/******************************\
| 			基礎功能
\******************************/
db Cross (Point a, Point b) { return a.x*b.y - a.y*b.x; }
db Cross (Point pt, Point a, Point b) { return Cross(a-pt, b-pt); }
db Dot (Point a, Point b) { return a.x*b.x + a.y*b.y; }
db Dot (Point pt, Point a, Point b) { return Dot (a-pt, b-pt); }
db Dist (Point a, Point b) { return mysqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); }
db Len (Point a) { return mysqrt(a.x*a.x + a.y*a.y); }
db Len2 (Point a) { return a.x*a.x + a.y*a.y; }
// 計算 pa 和 pb 的夾角,  就是求這個點看 a, b 所成的夾角, 測試 LightOJ 1203
db rad(Point p, Point a, Point b) { 
    return fabs(atan2(fabs(Cross(p, a, b)), Dot(p, a, b) )); 
}
/******************************\
| 			Complex補充
\******************************/
struct Complex {
    db x, y;
    Complex(db x = 0, db y = 0) : x(x), y(y) {}
    Complex operator + (Complex b) {return Complex(x + b.x, y + b.y);}
    Complex operator - (Complex b) {return Complex(x - b.x, y - b.y);}
    Complex operator * (Complex b) {return Complex(x * b.x - y * b.y, x * b.y + y * b.x);}
};
2. 線類
/*==================================================*\
| 使用Line類時,先將Point類的 + - Cross Dot Dist 補全
| 							Line 類
| 							類內函式
| Line(P s = o, e = o) 						- 建構函式 預設
| operator ==								- 兩條線段的關係, 需要同向
| Line(P s, db ang) 						- 建構函式 點 + 傾斜角
| Line(P a, P b, P c)						- 建構函式 ax + by + c = 0
| input()									- 讀入一條直線
| operator &								- 返回兩條直線的交點以及關係 pair<int, P> (0: 平行, 1: 重合, 2: 相交)
| adjust() 									- 改為方向, 直線指向上方
| length() 									- 返回線段長度
| 							類外函式
| *基礎操作
| angle(L v)								- 返回直線傾斜角db 0 <= ang < PI
| *幾何關係
| relation(P p, L v)						- 點和直線的關係 (1: 在左側, 2: 在右側, 3: 在直線上)
| segrelation(P p, L v)						- 點和線段的關係 (true: 線上段上, false: 不線上段上)? 
| parallel(L a, L b)						- 兩直線的平行關係 (true: 平行 重合, false: 相交)
| segrelationseg(L a, L b)					- 兩線段相交判斷 (2: 規範相交, 1: 非規範相交, 0: 不相交)
| relationseg(L a, L b) 					- 直線和線段的相交判斷 (2: 規範相交, 1: 非規範相交, 0: 不相交)
| relationline(L a, L b)					- 兩直線的關係 (0: 平行, 1: 重合, 2: 相交)	
| *交點
| pcrossll(L a, L b) 						- 兩直線的交點 (要保證兩直線不平行或重合)
| pcrossll(L a, L b, P& p)					- 兩直線的交點 (無需判斷兩直線的關係) (0: 平行, 1: 重合, 2: 相交)
| *距離
| disptl(L v, P p) 							- 點到直線的距離 db
| dispts(L v, P p) 							- 點到線段的距離 db
| dissts(L a, L b) 							- 線段到線段的距離 (保證兩條線段不相交) db
| *點線關係
| propl(P p, L v) 							- 返回點在直線上的投影 P, 同時也是垂足
| symmetrypoint(L v, P p) 					- 返回點關於直線的對稱點 P
\*==================================================*/
struct Line {
    Point s, e;
    Line (Point s=Point(0,0), Point e=Point(0,0)): s(s), e(e) {}
    bool operator == (Line v) { return (s==v.s) && (e==v.e); }
    // 點 和 傾斜角
    Line (Point p, db ang) {
        s = p;
        if (sgn(ang - PI/2) == 0) e = s + Point (0, 1);
        else e = s + Point (1, tan(ang));
    }
    // 一般式 ax + by + c = 0
    Line (db a, db b, db c) {
        if (sgn(a) == 0) {
            s = Point (0, -c/b);
            e = Point (1, -c/b);
        }else if (sgn(b) == 0) {
            s = Point (-c/a, 0);
            e = Point (-c/a, 1);
        }else {
            s = Point (0, -c/b);
            e = Point (1, (-c-a)/b);
        }
    }
    void input() { s.input(), e.input(); }
    // 返回兩條直線的交點以及關係
    pair<int, Point> operator & (Line r) {
        Point res = s;
        if(sgn(Cross(r.e - r.s, e - s)) == 0) { // 共線
            if(sgn(Cross(r.e - r.s, e - r.s)) == 0) return make_pair(1, res); // 1: 重合
            return make_pair(2, res); // 2: 平行
        }
        db t = Cross(r.s, s, r.e)/Cross(r.e-r.s, e-s);
        res.x += (e.x-s.x) * t;
        res.y += (e.y-s.y) * t;
        return make_pair(0, res); // 0: 相交
    }
    // 改為方向指向上方
    void adjust() { if (e < s) swap(s, e); }
    // 求線段的長度
   	db length() { return Dist (s, e); }
};
/******************************\
| 			基礎操作
\******************************/
// 返回直線傾斜角 0 <= ang < PI
db angle(Line v) {
    db k = atan2(v.e.y-v.s.y, v.e.x-v.s.x);
    if (sgn(k) < 0) k += PI;
    if (!sgn(k - PI)) k -= PI;
    return k;
}
/******************************\
| 			幾何關係
\******************************/
// 點和直線的關係 1: 在左側,  2: 在右側, 3: 在直線上
int relation(Point p, Line v) {
    int c = sgn(Cross(v.s, p, v.e));
    if (c < 0) return 1;
    else if (c > 0) return 2;
    else return 3;
}
// 點線上段上的判斷
bool segrelation(Point p, Line v) {
    return sgn(Cross(v.s, p, v.e)) == 0 && sgn(Dot(p-v.s, p-v.e)) <= 0;
}
// 兩向量平行  (對應直線平行或重合) 
bool parallel(Line a, Line b) { 
    return sgn(Cross(a.e-a.s, b.e-b.s)) == 0;
}
// 兩線段相交判斷 (2: 規範相交, 1: 非規範相交, 0: 不相交) 
int segrelationseg(Line a, Line b) {
    int d1 = sgn(Cross(a.s, a.e, b.s));
    int d2 = sgn(Cross(a.s, a.e, b.e));
    int d3 = sgn(Cross(b.s, b.e, a.s));
    int d4 = sgn(Cross(b.s, b.e, a.e));
    if ((d1^d2) == -2 && (d3^d4) == -2) return 2;
    return (d1 == 0 && sgn(Dot(b.s-a.s, b.s-a.e)) <= 0) ||
        (d2 == 0 && sgn(Dot(b.e-a.s, b.e-a.e)) <= 0) ||
        (d3 == 0 && sgn(Dot(a.s-b.s, a.s-b.e)) <= 0) ||
        (d4 == 0 && sgn(Dot(a.e-b.s, a.e-b.e)) <= 0);
}
// 直線和線段的相交判斷 (2: 規範相交, 1: 非規範相交, 0: 不相交) , b 是線段
int relationseg(Line a, Line b) {
    int d1 = sgn(Cross(a.s, a.e, b.s));
    int d2 = sgn(Cross(a.s, a.e, b.e));
    if ((d1^d2) == -2) return 2;
    return (d1 == 0 || d2 == 0);
}
// 兩直線的關係 (0: 平行, 1: 重合, 2: 相交) 
// 1. Line: parallel()
// 2. Line: relation()
int relationline(Line a, Line b) {
    if (parallel(a, b)) 
        return relation(a.s, b) == 3;
    return 2;
}
// 兩直線的交點 (要保證兩直線不平行或重合) , 測試: POJ1269 simply
// 如果要判斷是否相交, 建議用下面那個
/******************************\
| 			交點
\******************************/
Point pcrossll(Line a, Line b) {
    db k = Cross(a.s, b.s, b.e)/Cross(a.e-a.s, b.e-b.s);
    return a.s + (a.e-a.s)*k;
}
// 兩直線相交 (0: 平行, 1: 重合, 2: 相交) 
// 無需判斷兩直線的關係
int pcrossll(Line a, Line b, Point &p) {
    if (sgn(Cross(a.e - a.s, b.e - b.s)) == 0) 
        return sgn(Cross(b.s, a.s, b.e)) == 0;
    else {
        db k = Cross(a.s, b.s, b.e)/Cross(a.e-a.s, b.e-b.s);
        p = a.s + (a.e-a.s)*k;
        return 2;
    }
}
/******************************\
| 			距離
\******************************/
// 點到直線的距離
// 1. Line → length(...)=Point: Dist()
db disptl(Line v, Point p) {
    return fabs(Cross(v.s, p, v.e)) / v.length(); // length = [](){return Dist(v.s, v.e);}
}
// 點到線段的距離
// 1. Line: disptl(...)=Point: Dist()
db dispts(Line v, Point p) {
    if (sgn(Dot(p-v.s, v.e-v.s)) < 0 || sgn(Dot(p-v.e, v.s-v.e)) < 0)
        return min(Dist(p, v.s), Dist(p, v.e));
    return disptl(v, p);			// disptl = [](v, p){return fabs(Cross(v.s, p, v.e)) / Dist(v.s, v.e);}
}
// 線段到線段的距離, 前提是兩條線段不相交
// 1. Line: dispts(...(...))
db dissts(Line a, Line b) {
    return min(dispts(a, b.s), dispts(a, b.e));
}
// 返回點 p 在直線上的投影
// 1. Point: Len2
/******************************\
| 			點線關係
\******************************/
// 返回點在直線上的投影 P
// 1. Point: Len2()=Point: Len2()
Point propl(Point p, Line v) {
    Point t = v.e - v.s;
    return v.s + t * Dot(t, p-v.s) / Len2(t);		//Len2=(t)[] { return (t.x*t.x + t.y*t.y); }	
}
// 返回點 p 關於直線的對稱點
// 1. Line: propl(...)
Point symmetrypoint(Line v, Point p) {
    Point q = propl(p, v);
    return Point (2*q.x-p.x, 2*q.y-p.y);
}
4. 圓類
/*========================================================*\
| 
| 使用Circle類時
| 將Point類的 + - * / Cross Dot Dist 補全
| 將Line類的 基本結構 補全
| 							Circle 類
| 							類內函式
| Circle(P * 3) 								- 三角形的外接圓
| Circle(P * 3, bool t) 						- 三角形的內切圓
| input() 										- 讀入一個圓, 圓心+半徑
| operator == 									- 兩個圓的相等關係, 同心同半徑
| operator < 									- 用於排序, 先點後半徑, 符號與<同向
| area() 										- 返回圓的面積
| circumference()								- 返回圓的周長
| point(db rad)									- 圓上夾角為rad的一點
| 							類外函式
| *幾何關係
| relation(C c, P a) 							- 點和圓的關係 (0: 圓外, 1: 圓上, 2: 圓內) 
| relationseg(C c, L v)			 				- 線段和圓的關係, 返回交點個數
| relationline(C c, L v) 						- 直線和圓的關係, 返回交點個數
| relationcircle(C * 2) 						- 兩圓的關係 (5: 相離, 4: 外切, 3: 相交, 2: 內切, 1: 內含, 0: 重合) 
| *與圓的交點
| ppcrosslc(C a, L b, P& * 2)					- 直線與圓的交點
| ppcrosscc(C a, C b, P& * 2) 					- 兩圓相交的交點
| *與圓的面積
| areacircle(C * 2)								- 求兩圓相交的面積
| *與圓的切線
| ltangentpc(P p, C a, int clock)				- 過一點作圓的切線
| ltangentcc(C * 2, P* p1, P* p2)				- 求兩圓的公切線
| *特殊方法得到圓
| getcircle(P * 2, db r0, C& * 2)				- 得到過 a, b 兩點, 半徑為 r0 的兩個圓
| getcircle(L u, P q, db r0, C& * 2)			- 得到與直線 u 相切, 過點 q, 半徑為 r0 的圓
| getcircle(L u, L v, db r0, C& * 4)			- 得到與直線 u, v 相切, 半徑為 r0 的圓
| getcircle(C cx, C cy, db r0, C& * 2)			- 得到同時與不相交圓 cx, cy 相切, 半徑為 r0 的圓
| getcircle(P * 3, C& c);						- 三角形的外接圓
| getcircle(P * 3, bool t, C& c);				- 三角形的內切圓
\*========================================================*/
struct Circle {
    Point p; db r;
    Circle(Point p = Point(0, 0), db r = 0): p(p), r(r) {}
    // 三角形的外接圓, 利用兩條邊的中垂線得到圓心, 測試: UVA12304
    //1. Point: rotleft
    //2. Line: pcrossll
    Circle(Point a, Point b, Point c) {
        Point x = (a+b)/2, y = (b+c)/2;
        Line u = Line(x, x + (b-a).rotleft());
        Line v = Line(y, y + (c-b).rotleft());
        p = pcrossll(u, v);
        r = Dist(p, a);
    }
    // 三角形的內切圓, bool t 沒有作用, 只是為了和上面區別, 測試: UVA12304
    //1. Line: pcrossll, dispts
    Circle(Point a, Point b, Point c, bool t) {
        Line u, v;
        db m = atan2(b.y-a.y, b.x-a.x), n = atan2(c.y-a.y, c.x-a.x);
        u.s = a, u.e = u.s + Point (cos((n+m)/2), sin((n+m)/2));		//a, u
        m = atan2(a.y-b.y, a.x-b.x), n = atan2(c.y-b.y, c.x-b.x);
        v.s = b, v.e = v.s + Point (cos((n+m)/2), sin((n+m)/2));		//b, v
        p = pcrossll(u, v);
        r = dispts(Line(a, b), p);
    }
    void input() { p.input(); scanf("%lf", &r); }
    // 1. Point → == =()
    bool operator == (Circle c) const { 
        return p == c.p && sgn(r-c.r) == 0; // == = (p, c.p){return sgn(p.x - c.p.x)==0 && sgn(p.y - c.p.y)==0;}
    } 
    bool operator < (Circle c) const { return p < c.p || (p == c.p && sgn(r-c.r) < 0); }
    // 面積
    db area() { return PI*r*r; }
    // 周長
    db circumference() { return 2*PI*r; }
    // 圓的引數方程
    Point point(db rad) {
        return Point(p.x+cos(rad)*r, p.y+sin(rad)*r);
    }
};
/******************************\
|			幾何關係
\******************************/
// 點和圓的關係 (0: 圓外, 1: 圓上, 2: 圓內) 
int relation(Circle c, Point a) {
    db dst = Dist(a, c.p);
    if (sgn(dst - c.r) < 0) return 2;
    else if (sgn(dst - c.r) == 0) return 1;
    return 0;
}
// 線段和圓的關係, 比較的是圓心到線段的距離和半徑的關係
// 1. Line: dispts
int relationseg(Circle c, Line v) {
    db dst = dispts(v, c.p);
    if (sgn(dst - c.r) < 0) return 2;
    else if (sgn(dst - c.r) == 0) return 1;
    return 0;
}
// 直線和圓的關係, 比較的是圓心到直線的距離和半徑的關係
// 1. Line: disptl
int relationline(Circle c, Line v) {
    db dst = disptl(v, c.p);
    if (sgn(dst - c.r) < 0) return 2;
    else if (sgn(dst - c.r) == 0) return 1;
    return 0;
}
// 兩圓的關係 (5: 相離, 4: 外切, 3: 相交, 2: 內切, 1: 內含, 0: 重合) 
int relationcircle(Circle a, Circle b) {
    db d = Dist(a.p, b.p);
    if (sgn(d - a.r - b.r) > 0) return 5;
    if (sgn(d - a.r - b.r) == 0) return 4;
    db l = fabs(a.r - b.r);
    if (sgn(d - a.r - b.r) < 0 && sgn(d - l) > 0) return 3;
    if (sgn(d - l) == 0) return 2;
    if (sgn(d - l) < 0) return 1;
    return 0;
}
/******************************\
| 			與圓的交點
\******************************/
// 直線與圓的交點: 利用圓中的直角三角形求解
// 1. Point: trunc
// 2. Line: propl
int ppcrosslc(Circle a, Line b, Point &p1, Point &p2) {
    Point p = propl(a.p, b);				//中點
    db d = Dist(p, a.p);
    if (sgn(d - a.r) > 0) return 0;			//相離
    if (sgn(d - a.r) == 0) {				//相切
        p1 = p2 = p;
        return 1;
    }
    Point dir = (b.s - b.e).trunc(1);		//相交
    db len = mysqrt(a.r*a.r - d*d);
    p1 = p - dir*len, p2 = p + dir*len;
    return 2;
} 
// 兩圓相交的交點: 利用餘弦定理
// 1. Point -> == = Fun()
// 2. Circle: point
int ppcrosscc(Circle a, Circle b, Point &p1, Point &p2) {
    db d = Dist(a.p, b.p);
    if (!sgn(d)) {
        if (!sgn(a.r - b.r)) return -1; // 兩圓重合
        else return 0; // 無交點, 同心圓
    }
    if (sgn(a.r + b.r - d) < 0) return 0; // 外離
    if (sgn(fabs(a.r-b.r) - d) > 0) return 0;
    Vector dir = b.p - a.p;
    db ang = atan2(dir.y, dir.x);		//要得到一個 (-PI, PI] 內的角
    db rad = acos((a.r*a.r+d*d-b.r*b.r)/(2*a.r*d));
    p1 = a.point(ang - rad);
    p2 = a.point(ang + rad);
    return 1 + ! (p1 == p2);				//== = [](){return sgn(p1.x - p2.x) == 0 && sgn(p1.y - p2.y) == 0;}
}
/******************************\
| 			與圓的面積
\******************************/
// 求兩圓相交的面積
// 1. Circle: relationcircle
// 1. Circle → area
db areacircle(Circle a, Circle b) {
    int rel = relationcircle(a, b);
    if (rel >= 4) return 0.0;
    if (rel <= 2) return min(a.area(), b.area());
    db d = Dist(a.p, b.p);
    db hf = (a.r + b.r + d) / 2.0; 
    db ss = 2 * sqrt(hf*(hf-a.r)*(hf-b.r)*(hf-d)); // 海倫公式
    db a1 = acos((a.r*a.r + d*d - b.r*b.r)/(2*a.r*d)); // 餘弦定理, 求的是角度的一般
    db a2 = acos((b.r*b.r + d*d - a.r*a.r)/(2*b.r*d));
    a1 = a1*r*r, a2 = a2*r*r;   // 扇形面積, 這裡不用乘以0.5
    return a1 + a2 - ss;
}
/******************************\
| 			與圓的切線
\******************************/
// 過一點作圓的切線(不用保證一定是圓外的點), 測試: POJ1375
// 1. Point: rotleft, rotright, trunc
// 2. Circle: relation
int ltangentpc(Circle c, Point p, Line &u,Line &v){
	int t = relation(c, p);
	Vector dir = p - c.p;
	if(t == 2) return 0;
	if(t == 1){
		u = Line(p, p + dir.rotleft());
		v = u;
		return 1;
	}
	db d = Dist(c.p, p);
	db l = c.r * c.r / d;
	db h = sqrt(c.r * c.r - l * l);
	u = Line(p, c.p + (dir.trunc(l) + dir.rotleft().trunc(h)));
	v = Line(p, c.p + (dir.trunc(l) + dir.rotright().trunc(h)));
	return 2;
}
// 求兩圓的公切線, 測試: UVA10674
// 1. Circle: point
int ltangentcc(Circle a, Circle b, Point* p1, Point* p2) {
    if (a.r < b.r) swap(a, b), swap(p1, p2);
    db diff = a.r - b.r;
    db sum = a.r + b.r;
    db d2 = (a.p.x-b.p.x)*(a.p.x-b.p.x) + (a.p.y-b.p.y)*(a.p.y-b.p.y);
    // 情況一: 重合
    if (a.p == b.p && sgn(a.r - b.r) == 0)  return -1;
    // 情況二: 內含
    if (sgn(d2 - diff*diff) < 0) return 0;
    // 情況三: 內切
    int res = 0;
    db base = atan2(b.p.y-a.p.y, b.p.x-a.p.x);
    if (sgn(d2 - diff*diff) == 0) {
        p1[res] = a.point (base), p2[res] = b.point (base); res ++;
        return 1;
    }
    // 情況四: 相交 (有外公切線) 
    db ang = acos((a.r-b.r)/mysqrt(d2));
    p1[res] = a.point (base + ang), p2[res] = b.point (base + ang), res ++;
    p1[res] = a.point (base - ang), p2[res] = b.point (base - ang), res ++;
    // 情況五: 外切 (有一條內公切線) 
    if (sgn(sum*sum - d2) == 0) {
        p1[res] = p2[res] = a.point(base), res ++;
    }
    // 情況六: 外離 (有兩條內公切線) 
    else if (sgn(sum*sum - d2) < 0) {
        ang = acos ((a.r + b.r) / mysqrt(d2));
        p1[res] = a.point (base + ang), p2[res] = b.point (PI + base + ang), res ++;
        p1[res] = a.point (base - ang), p2[res] = b.point (PI + base - ang), res ++;
    }
    return res;
}
/******************************\
| 			特殊方法得到圓
\******************************/
// 得到過 a, b 兩點, 半徑為 r0 的兩個圓, 測試: UVA12304
// 1. Circle: ppcrosscc(...)
int getcircle(Point a, Point b, db r0, Circle &c1, Circle &c2) {
    Circle x(a, r0), y(b, r0);
    int t = ppcrosscc(x, y, c1.p, c2.p);
    if (! t) return 0;
    c1.r = c2.r = r0;
    return t;
}
// 得到與直線 u 相切, 過點 q, 半徑為 r0 的圓, 測試: UVA12304
// 1. Point: rotleft, rotright, trunc, == = Fun()
// 2. Line: disptl(...)=Point: Dist
// 3. Circle: ppcrosslc(...)
int getcircle(Line u, Point p, db r0, Circle &c1, Circle &c2) {
    db dis = disptl(u, p);			// disptl=[](u, p){return fabs(Cross(u.s, p, u.e)) / Dist(u.s, u.e);}
    if (sgn(dis - r0*2) > 0) return 0;
    Point down = (u.e-u.s).rotleft().trunc(r0);
    Point up = (u.e-u.s).rotright().trunc(r0);	//兩個對應點
    if (!sgn(dis)) {
        c1.p = p + down;
        c2.p = p + up;
        c1.r = c2.r = r0;
        return 2;
    }
    Line u1(u.s + down, u.e + down), u2(u.s + up, u.e + up);	//兩條線
    Circle cc(p, r0);
    Point p1, p2;
    if (! ppcrosslc(cc, u1, p1, p2))	//只會與一條線交
        ppcrosslc(cc, u2, p1, p2);
    c1 = Circle(p1, r0), c2 = Circle(p2, r0);
    if (p1 == p2) return 1; 			//相切	== = [](){return sgn(p1.x - p2.x) == 0 && sgn(p1.y - p2.y) == 0;}
    return 2;							//相交
}
// 得到與直線 u, v 相切, 半徑為 r0 的圓, 測試: UVA12304
// 1. Point: rotleft, rotright, trunc
// 2. Line: parallel=Fun(), pcrossll
int getcircle(Line u, Line v, db r0, Circle &c1, Circle &c2, Circle &c3, Circle &c4) {
    if (parallel(u, v) == 1) return 0;				// parallel=[](u, v){return sgn(Cross(u.e - u.s, v.e - v.s)) == 0}
    Point down1 = (u.e-u.s).rotleft().trunc(r0);
    Point down2 = (v.e-v.s).rotleft().trunc(r0);
    Point up1 = (u.e-u.s).rotright().trunc(r0);
    Point up2 = (v.e-v.s).rotright().trunc(r0);		//得到四個點
    Line u1(u.s + down1, u.e + down1), u2(u.s + up1, u.e + up1);
    Line v1(v.s + down2, v.e + down2), v2(v.s + up2, v.e + up2);	//得到四條平行線
    c1.r = c2.r = c3.r = c4.r = r0;
    c1.p = pcrossll(u1, v1);
    c2.p = pcrossll(u1, v2);
    c3.p = pcrossll(u2, v1);
    c4.p = pcrossll(u2, v2);										//四個交點對應四個圓心
    return 4;
}
// 得到同時與不相交圓(要保證不相交) cx, cy 相切, 半徑為 r0 的圓, 測試: UVA12304
// 1. Circle: ppcrosscc(...)
int getcircle(Circle cx, Circle cy, db r0, Circle &c1, Circle &c2) {
    Circle x(cx.p, r0+cx.r), y(cy.p, r0+cy.r);	//兩圓交點即為圓心
    int t = ppcrosscc(x, y, c1.p, c2.p);
    if (! t) return 0;		//無圓
    c1.r = c2.r = r0;
    return t;
}
// 三角形的外接圓, 利用兩條邊的中垂線得到圓心, 測試: UVA12304
//1. Point: rotleft
//2. Line: pcrossll
int getcircle(Point a, Point b, Point c, Circle &c1) {
    Point x = (a+b)/2, y = (b+c)/2;
    Line u = Line(x, x + (b-a).rotleft());
    Line v = Line(y, y + (c-b).rotleft());	//兩條中垂線
    c1.p = pcrossll(u, v);					//交點為圓心
    c1.r = Dist(c1.p, a);
    return 1;
}
// 三角形的內切圓, bool t 只是為了和上面區別, 測試: UVA12304
//1. Line: pcrossll, dispts
int getcircle(Point a, Point b, Point c, bool t, Circle &c1) {
    Line u, v;
    db m = atan2(b.y-a.y, b.x-a.x), n = atan2(c.y-a.y, c.x-a.x);//兩個角
    u.s = a, u.e = u.s + Point (cos((n+m)/2), sin((n+m)/2));		//a, u
    m = atan2(a.y-b.y, a.x-b.x), n = atan2(c.y-b.y, c.x-b.x);
    v.s = b, v.e = v.s + Point (cos((n+m)/2), sin((n+m)/2));		//b, v //兩條角平分線
    c1.p = pcrossll(u, v);
    c1.r = dispts(Line(a, b), c1.p);
    return 1;
}

二、凸包

1. 多邊形
// 判斷點在多邊形內部 1, 測試: POJ1584
// 1. Point -> == = Fun()
int relation(Point pt, Point *p, int n) {
    p[n] = p[0];
    for (int i = 0; i < n; i ++) 
        if (pt == p[i]) // == = [](pt, p[i]){return sgn(pt.x - p[i].x) == 0 && sgn(pt.y - p[i].y) == 0;}
            return 3; 	// 3: 點在多邊形的頂點上
    for (int i = 0; i < n; i ++) 
        if (segrelation(pt, Line(p[i], p[i+1]))) return 2;   // 2: 點在多邊形的邊上 
    int num = 0;
    for (int i = 0; i < n; i ++) {
        int j = i + 1;
        int c = sgn(Cross(pt-p[j], p[i]-p[j])); // 用來判斷點和向量的位置關係
        int u = sgn(p[i].y - pt.y);    // 判斷水平直線有沒有穿過當前線段
        int v = sgn(p[j].y - pt.y);   
        if (c > 0 && u < 0 && v >= 0) num ++;
        if (c < 0 && u >= 0 && v < 0) num --;
    }
    return num != 0;   // 1: 點在內部;  0: 點在外部
}
// 求多邊形的周長
db polygonperimeter(Point* p, int n) {
    p[n] = p[0];
    db ans = 0;
    for(int i = 0; i < n; i ++)
        ans += Dist(p[i], p[i+1]);
    return ans;
}
// 求多邊形的面積 (三角剖分) 
db polygonarea(Point* p, int n) {
    db ans = 0;
    p[n] = p[0];
    for (int i = 0; i < n; i ++) 
        ans += Cross(p[i], p[i+1]);
    return ans / 2;           // 面積有正負, 需根據題意來判斷要不要取絕對值
}
// 求多邊形的重心 (三角剖分) 
Point polygoncenter(Point* p, int n) {
    Point ans = O;
    p[n] = p[0];
    db area = polygonarea(p, n);
    if (sgn(area) == 0) return ans;
    for (int i = 0; i < n; i ++) // 重心: (p[i]+p[i+1])/3, 有向面積: Cross(p[i], p[i+1])/2
        ans = ans + (p[i]+p[i+1])*Cross(p[i], p[i+1]); 
    return ans/area/6;
}
2. 求凸包
// Andrew 演算法
// 座標排序 O(nlogn), 測試: 洛谷P2742;凸包穩定相關: POJ1228
// 這裡有兩種排序方法
// 1. 優先按 y 排序, 如果 y 相同, 按 x 排序, 起點是最下面的點, 如下面程式碼體現
// 2. 優先按 x 排序, 如果 x 相同, 按 y 排序, 起點是最左邊的點
bool operator < (Point a, Point b) { 
	if (sgn(a.y - b.y) == 0) return a.x < b.x;
    return a.y < b.y;
}
int andrew(Point *p, Point *ch, int n) {  // 返回值是凸包的頂點數
    sort (p, p + n);   // 排序
    n = unique(p, p + n) - p; // 去除重複的點
    int v = 0;
    // 求下凸包, 如果 p[i] 是右拐彎的, 則這個點不在凸包中, 往回退
    for (int i = 0; i < n; i ++) {
        while (v > 1 && sgn(Cross(ch[v-2], ch[v-1], p[i])) <= 0) v --;
        ch[v ++] = p[i];
    }
    int j = v;
    // 求上凸包
    for (int i = n - 2; i >= 0; i --) {
        while (v > j && sgn(Cross(ch[v-2], ch[v-1], p[i])) <= 0) v --;
        ch[v ++] = p[i];
    }
    if (n > 1) v --;
    return v;
}


//Graham 演算法
//極角排序O(nlogn), 測試: 洛谷P2742
// 1. 選擇一個最左下的點作為基準點, 那麼這點一定在凸包內。
// 2. 接著以這個基準點為基準, 將剩餘的點按極角從小到大排序, 可以通過叉積實現。
// 3. 若極角相同, 則按照到基準點距離從小到大排序。
Point base; // 全域性變數
bool cmp(Point a, Point b) {
    int c = sgn(Cross(base, a, b));
    if (c == 0) 
        return Dist(base, a) < Dist(base, b);
    return c < 0;
}
int graham(Point* p, Point* ch, int n) {
    base = p[0]; // 基準點不參與排序, 一般我們放在 p[0]
    sort (p + 1, p + n, cmp);
    int v = 0;
    ch[v ++] = p[0]; // 基準點一定參與構成凸包
    for (int i = 1; i < n; i ++) {
        while (v > 1 && sgn(Cross(ch[v-2], ch[v-1], p[i])) <= 0) v --;
        ch[v ++] = p[i];
    }
    return v;
}

// 是否構成凸包
// 順逆時針都可以判斷
// 判斷是否是凸包, 方向不一定是逆時針
bool isconvex(Point* p, int n){
    int dir = 0;
    p[n] = p[0], p[n+1] = p[1];
    for(int i = 1; i <= n; i ++) {
        int u = sgn(Cross(p[i] - p[i-1], p[i+1] - p[i]));
        if(!dir) dir = u;
        if(u * dir < 0) return false;
    }
    return true;
}

// 將凸包變為逆時針
void change(Point* p, int n) {
    p[n] = p[0];
    for (int i = 0; i < n - 1; i ++) {
        int c = sgn (Cross (p[i], p[i+1], p[i+2]));
        if (c > 0) return ;  // 方向正確
        if (c < 0) {         // 方向錯誤, 將整個凸包反轉
            for (int j = 0, k = n - 1; j < k; j ++, k --) 
                swap(p[j], p[k]);
            return ;
        }
    }
}

// 直線 u 與凸包左側形成新凸包
// 單次時間複雜度 $O(n)$​​ , 測試: HDU3982
// 類似半平面交, 其實也可以用來求半平面交, 複雜度 O(n^2)
int convexcut(Line u, Point* p, int n) {
    int v = 0;
    p[n] = p[0];
    static Point ch[N];
    for (int i = 0; i < n; i ++) {
        int d1 = sgn(Cross(u.s, u.e, p[i]));
        int d2 = sgn(Cross(u.s, u.e, p[i+1]));
        if (d1 >= 0) ch[v ++] = p[i]; // 在左側則壓入點
        // 異號說明他穿過這條直線
        if (d1*d2 < 0) ch[v ++] = pcrossll(u, Line(p[i], p[i+1]));   
    }// 拷貝答案
    for (int i = 0; i < v; i ++) p[i] = ch[i]; 
    return v;
}
3. 點和凸包的關係

時間複雜度 \(O(log\ n)\) , 測試: UVALive - 7281

//2: 裡邊, 1: 上邊, 0: 外邊
int relation(Point* p, int n, Point pt) {
    // l 取 1 的原因是要以p[0]作為基準點, r 取 n-2 的原因是下面要進行mid+1
    int l = 1, r = n - 2; 
    while (l <= r) {
        int mid = (l + r) >> 1;
        int d1 = sgn(Cross(p[0], p[mid], pt));
        int d2 = sgn(Cross(p[0], p[mid+1], pt));
        if (d1 >= 0 && d2 <= 0) { // 找到 pt 所在的三角區域
            // 如果在左側或在邊上, 說明在內部
            int c = sgn(Cross(p[mid], p[mid+1], pt));
            if (c > 0) return 2;
            else if (c == 0) return 1;
            else return 0; // 否則不在
        }
        else if (d1 < 0) r = mid - 1; // 在右邊
        else l = mid + 1; // 在左邊
    }
    return 0;
}
4. 過凸包外一點求凸包的切線

時間複雜度 \(O(log\ n)\) , 測試: Codeforces - gym - 101201E (為防止 \(wa\) 哭, 請使用 \(long\ double\) 或者 \(long\ long\))

// 二分查詢斜率最值對應的點, w 等於 1 表示找最小值, w 等於 -1 表示找最大值
// l 到 r 為查詢區間, pt 為凸包外一點
int minslope(Point* p, int l, int r, Point pt, int w) {
    while (l < r) {
        int mid = (l + r) >> 1;
        if (sgn(Cross(pt, p[mid], p[mid+1])) * w >= 0) r = mid;
        else l = mid + 1;
    }
    return l;
}
// w = 1:  二分查詢第一個大於等於 x 對應的點
// w = -1: 二分查詢第一個小於等於 x 對應的點
int border(Point* p, int l, int r, db x, int w) {
    while (l < r) {
        int mid = (l + r) >> 1;
        if (sgn(x - p[mid].x) * w <= 0) r = mid;
        else l = mid + 1;
    }
    return l;
}

// 使用該函式之前要判斷點是否在凸包外
pair<int, int> pptangentpcon(Point* p, int n, Point pt, int Rx) {
    int L, R, Mid;
    if (sgn(pt.x - p[0].x) < 0) {  // 情況一: 點在凸包的左邊
        L = minslope(p, 0, Rx, pt, 1), R = minslope(p, Rx, n, pt, -1);
    }
    else if (sgn(pt.x - p[Rx].x) > 0) {  // 情況二: 點在凸包的右邊
        L = minslope(p, 0, Rx, pt, -1), R = minslope(p, Rx, n, pt, 1);
    }
    else if (Cross(pt, p[0], p[Rx]) > 0) { // 情況三: 點在凸包的上方
        Mid = border(p, Rx, n, pt.x, -1);
        L = Mid > Rx ? minslope(p, Rx, Mid-1, pt, -1) : Mid;
        R = minslope(p, Mid, n, pt, 1);
    }
    else { 								// 情況四: 點在凸包的下方
        Mid = border(p, 0, Rx, pt.x, 1);
        L = Mid > 0 ? minslope(p, 0, Mid-1, pt, -1) : 0;
        R = minslope(p, Mid, Rx, pt, 1);
    }
    return make_pair(L, R); // 返回找到的兩個切點
}

三、半平面交

1. 半平面類
struct Plane {
    Point p; /// 平面上的一點
    Vector v; /// 方向向量
    db ang; /// 從 x 軸轉向 v 方向的夾角
    Plane(Point p=o, Vector v=Vector(1,0)): p(p), v(v) { ang = atan2(v.y, v.x); }
    void calangle() { ang = atan2(v.y, v.x); }
    bool operator < (Plane r) { return sgn(ang - r.ang) > 0; }
};
/// 點 p 線上 L 的左邊, 即點 p 在 L 的外邊
bool onleft(Plane L, Point p) { return sgn(Cross(L.v, p - L.p)) > 0; }
/// 判斷點是否在直線上
bool online(Plane L, Point pt) { return sgn(Cross(pt - L.p, L.v)) == 0; }
/// 判斷點是否不在直線右邊
bool notonright(Plane L, Point pt) { return sgn(Cross(L.v, pt - L.p)) >= 0;  }
/// 兩直線交點
Point pcrossll(Plane a, Plane b) {
    db t = Cross(b.v, a.p - b.p)/Cross(a.v, b.v);
    return a.p + a.v * t;
}
2. 半平面交演算法

極角排序 \(O(nlog\ n)\) , 測試: POJ1279, HDU3982

/// 半平面交, 返回的凸包在 ch 陣列
int HPI(Plane* L, Point *ch, int n) {
    sort(L, L + n); /// 極角排序
    int h = 0, t = 0, v = 0;
    static Plane q[N]; /// 雙端佇列
    static Point p[N]; /// 兩個相鄰半平面的交點
    q[h] = L[0];
    for(int i = 1; i < n; i ++) {
        /// 刪除隊尾的半平面
        while(h < t && ! onleft(L[i], p[t-1])) t --;
        /// 刪除隊首的半平面
        while(h < t && ! onleft(L[i], p[h])) h ++;
        q[++ t] = L[i]; /// 將當前的半平面加入雙端佇列的尾部
        /// 極角相同的兩個半平面保留左邊
        if(sgn(Cross(q[t].v, q[t-1].v)) == 0) {
            t --;
            if(onleft(q[t], L[i].p)) q[t] = L[i];
        }
        /// 計算佇列尾部的半平面交點
        if(h < t) p[t - 1] = pcrossll(q[t-1], q[t]);
    }
    /// 刪除佇列尾部無用的半平面
    while(h < t && ! onleft(q[h], p[t-1])) t --;
    if(t - h <= 1) return 0; /// 空集
    p[t] = pcrossll(q[t], q[h]); /// 計算佇列首尾部的交點
    for(int i = h; i <= t; i ++) ch[v ++] = p[i]; /// 複製
    return v; /// 返回凸多邊形大小
}
3. 判斷是否存在核

單獨一個點也算時, \(onleft\) 函式改為 \(notonright\), \(O(nlog\ n)\) , 測試: POJ3130

bool HPI_Core(Plane* L, int n){
    sort(L, L + n);
    int h = 0, t = 0;
    static Plane q[N];
    static Point p[N];
    q[0] = L[0];
    for(int i = 1; i < n; i ++) {
        while(h < t && ! onleft(L[i], p[t-1])) t --;
        while(h < t && ! onleft(L[i], p[h])) h ++;
        q[++ t] = L[i];
        if(sgn(Cross(q[t].v, q[t-1].v)) == 0) {
            t --;
            if(onleft(q[t], L[i].p)) q[t] = L[i];
        }
        if(h < t) p[t-1] = pcrossll(q[t], q[t-1]);
    }
    while(h < t && ! onleft(q[h], p[t-1])) t --;
    return t - h + 1 > 2;
}

四、旋轉卡殼

1. 平面最近點對

時間複雜度 \(O(nlog^2n)\) , 測試: 洛谷P7883

// 回撥函式遜爆啦, 我們改用lambda表示式, 時間上快了一些
auto cmpy = [](Point A, Point B)->bool { 
    return sgn(A.y - B.y) < 0; 
};
auto cmpxy = [](Point A, Point B)->bool { 
    return sgn(A.x - B.x) < 0 || (sgn(A.x - B.x) == 0 && sgn(A.y - B.y) < 0); 
};

Point p[N], tmp_p[N];
db closetpair(int l, int r) {  // 分治前先排序
    db dis = INF;
    if (l == r) return dis; 		// 只剩一個點  
    if (l + 1 == r) return Dist(p[l], p[r]); // 只剩兩個點
    int mid = (l + r) >> 1;         // 分治
    db d1 = closetpair(l, mid);   // 求 S1 內的最近點對
    db d2 = closetpair(mid+1, r); // 求 S2 內的最近點對
    dis = min(d1, d2);
    int k = 0;
    for (int i = l; i <= r; i ++)      // 在 S1 和 S2 中間附近找可能的最小點對
        if (fabs(p[mid].x - p[i].x) <= dis)   // 按 x 座標來找
            tmp_p[k ++] = p[i];
    sort (tmp_p, tmp_p + k, cmpy);    // 按 y 座標排序, 用於剪枝. 這裡不能按 x 座標排序
    for (int i = 0; i < k; i ++) 
        for (int j = i + 1; j < k ;j ++) {
            if (tmp_p[j].y - tmp_p[i].y >= dis) break;  // 剪枝
            dis = min(dis, Dist(tmp_p[i], tmp_p[j]));
        }
    return dis; // 返回最小值
}
2. 凸包的直徑

實際上也是在求平面最遠點對 \(O(n)\) , 測試: POJ2187

// RC: rotatingcalipers 旋轉卡殼
db rotatingcalipers(Point* p, int n) {
    db res = 0;
    if (n == 2) return Dist(p[0], p[1]);
    p[n] = p[0];
    for (int i = 0, r = 2; i < n; i ++) {
        while (sgn (Cross (p[i], p[i+1], p[r]) - Cross (p[i], p[i+1], p[r+1])) < 0) 
            if (++ r >= n) r -= n;
        res = max(res, max(Dist(p[r], p[i]), Dist(p[r], p[i+1])));
    }
    return res;
}
3. 凸包的寬度

時間複雜度 \(O(n)\)​ , 測試: Codeforces-gym-101635K

// 演算法和求兩凸包間的最近距離類似, 只需做一些小的改變
db rotatingcalipers(Point* p, int n) {
    db res = 2e18;
    if (n == 2) return 0;
    p[n] = p[0];
    for (int i = 0, r = 2; i < n; i ++) {
        while (sgn (Cross (p[i], p[i+1], p[r]) - Cross (p[i], p[i+1], p[r+1])) < 0) 
            if (++ r >= n) r -= n;   // 尋找對踵點
        res = min(res, disptl(p[i], p[i+1], p[r])); // 改成點到直線的距離
    }
    return res;
}
4. 兩凸包間的最近距離

前提是兩個凸包無交集, 時間複雜度 \(O(n)\) , 測試: POJ3608

// 由於兩凸包的位置關係不確定, 我們要交換 A 和 B 再求一遍最近距離, 兩次取最小
db rotatingcalipers(Point* A, int n, Point* B, int m) {
    int u = 0, v = 0;
    db tmp, res = 1e18;
    A[n] = A[0], B[m] = B[0];
    for (int i = 1; i < n; i ++)
        if (sgn(A[i].y - A[u].y) < 0) u = i; // 尋找 A 最低點
    for (int i = 1; i < m; i ++) 
        if (sgn(B[i].y - B[v].y) > 0) v = i; // 尋找 B 最高點
    for (int i = 0; i < n; i ++) {
        while (sgn (tmp = Cross(A[u], A[u+1], B[v]) - Cross(A[u], A[u+1], B[v+1])) < 0) 
            if (++ v >= m) v -= m;
        if (sgn (tmp) == 0) res = min(res, dissts(A[u], A[u+1], B[v], B[v+1]));
        else res = min(res, dispts(A[u], A[u+1], B[v]));
        if (++ u >= n) u -= n;
    }
    return res;
}
5. 兩凸包間的最遠距離

前提是兩個凸包無包含關係, 時間複雜度 \(O(n)\)
演算法和求兩凸包間的最近距離類似, 只需做一些小的改變, 無測試, 正確性尚待商榷

// 由於兩凸包的位置關係不確定, 我們要交換 A 和 B 再求一遍最遠距離, 兩次取最大
db rotatingcalipers(Point* A, int n, Point* B, int m) {
    int u = 0, v = 0;
    db tmp, res = 1e18;
    A[n] = A[0], B[m] = B[0];
    for (int i = 1; i < n; i ++)
        if (sgn(A[i].y - A[u].y) < 0) u = i; // 尋找 A 最低點
    for (int i = 1; i < m; i ++) 
        if (sgn(B[i].y - B[v].y) > 0) v = i; // 尋找 B 最高點
    for (int i = 0; i < n; i ++) {
        while (sgn (tmp = Cross(A[u], A[u+1], B[v]) - Cross(A[u], A[u+1], B[v+1])) < 0) 
            if (++ v >= m) v -= m;
        if (sgn (tmp) == 0) {
            res = max(res, dispts(A[u], B[v], B[v+1])); // 求最值處修改
        	res = max(res, dispts(A[u+1], B[v], B[v+1]));
        }else res = max(res, dispts(A[u], A[u+1], B[v])); // 另一處修改
        if (++ u >= n) u -= n;
    }
    return res;
}
6. 凸包的最小面積矩形覆蓋

時間複雜度 \(O(n)\) , 測試: HDU5251

db rotatingcalipers(Point* p, int n) {
    int R = 1, U = 1, L;
    db ans = 1e18;
    p[n] = p[0];
    for (int i = 0; i < n; i ++) {
        while (sgn (Cross(p[i], p[i+1], p[U]) - Cross(p[i], p[i+1], p[U+1])) < 0) 
            if (++ U >= n) U -= n; // 尋找最高點
        while (sgn (Dot (p[i], p[i+1], p[R]) - Dot (p[i], p[i+1], p[R+1])) <= 0) 
            if (++ R >= n) R -= n; // 尋找最右端的點
        if (i == 0) L = R;
        while (sgn (Dot (p[i], p[i+1], p[L]) - Dot (p[i], p[i+1], p[L+1])) >= 0)
            if (++ L >= n) L -= n; // 尋找最左端的點
        db area = fabs(Cross(p[i], p[i+1], p[U]));
        db k = fabs(Dot(p[i], p[i+1], p[R]) - Dot(p[i], p[i+1], p[L]));
        k /= (p[i] - p[i+1]).Len2();
        ans = min(ans, area * k);  // 更新框出的矩形的面積
    }
    return ans;
}
7. 凸包的最小周長矩形覆蓋

時間複雜度 \(O(n)\) , 測試: UVA12307
**注意: **凸包的最小周長矩形覆蓋和最小面積覆蓋不一定是同一個矩形, 兩者沒有直接關係

// 演算法實現和求凸包最小面積矩形覆蓋基本一致, 只需做一些小的修改
db rotatingcalipers(Point* p, int n) {
    int R = 1, U = 1, L;
    db ans = 1e18;
    p[n] = p[0];
    for (int i = 0; i < n; i ++) {
        while (sgn (Cross(p[i], p[i+1], p[U]) - Cross(p[i], p[i+1], p[U+1])) < 0) 
            if (++ U >= n) U -= n;
        while (sgn (Dot (p[i], p[i+1], p[R]) - Dot (p[i], p[i+1], p[R+1])) <= 0) 
            if (++ R >= n) R -= n;
        if (i == 0) L = R;
        while (sgn (Dot (p[i], p[i+1], p[L]) - Dot (p[i], p[i+1], p[L+1])) >= 0)
            if (++ L >= n) L -= n;
        db area = fabs(Cross(p[i], p[i+1], p[U])), d = (p[i] - p[i+1]).Len();
        db k = fabs(Dot(p[i], p[i+1], p[R]) - Dot(p[i], p[i+1], p[L]));
        db height = area / d, width = k / d; 	// 得到高和寬
        // S = min(S, height * width);  面積
        ans = min(ans, (height + width) * 2);  // 維護周長的最小值
    }
    return ans;
}

五、三角剖分

1. 兩個凸包的面積交

測試: HDU3060

/// 兩個凸包的面積交, 兩個凸包都要是逆時針
db CPI_Area(Point* a, int na, Point* b, int nb) {
    static Point p[N], tmp[N];
    int tn, sflag, eflag;
    a[na] = a[0], b[nb] = b[0];
    memcpy(p, b, sizeof(Point)*(nb+1));
    /// 整個過程類似半平面交
    /// 列舉一個凸包的所有邊對另一個凸包進行切割
    for(int i = 0; i < na && nb > 2; i ++) {
        sflag = sgn(Cross(a[i], a[i+1], p[0]));
        for(int j = tn = 0; j < nb; j ++, sflag = eflag) {
            if(sflag >= 0) tmp[tn ++] = p[j]; /// 當前頂點不在右側, 直接保留
            eflag = sgn(Cross(a[i], a[i+1], p[j+1]));
            /// 異號說明兩直線相交, 計算交點切割邊
            if((sflag ^ eflag) == -2)
                tmp[tn ++] = (Line(a[i], a[i+1]) & Line(p[j], p[j+1])).second;
        }
        memcpy(p, tmp, sizeof(Point)*tn);
        nb = tn, p[nb] = p[0];
    }
    if(nb < 3) return 0;
    /// 返回切割後的面積就是面積交
    return polygonarea(p, nb);
}
2. 兩個多邊形的面積並

測試: HDU3060

/// 兩個多邊形的面積並
db SPI_Area(Point* a, int na, Point* b, int nb) {
    Point t1[4], t2[4];
    db res = 0;int num1, num2;
    a[na] = t1[0] = a[0], b[nb] = t2[0] = b[0];
    /// 以各自的 0 處的點作為基本點, 並保證整個三角形都是都是逆時針
    for(int i = 2; i < na; i ++) {
        t1[1] = a[i-1], t1[2] = a[i];
        num1 = sgn(Cross(t1[0], t1[1], t1[2]));
        if(num1 < 0) swap(t1[1], t1[2]);
        for(int j = 2; j < nb; j ++) {
            t2[1] = b[j-1], t2[2] = b[j];
            num2 = sgn(Cross(t2[0], t2[1], t2[2]));
            if(num2 < 0) swap(t2[1], t2[2]);
            res += CPI_Area(t1, 3, t2, 3) * num1 * num2;
        }
    }
    return fabs(res);
}
3. 圓與有向三角形的面積交

測試: 牛客20324 J題, POJ2986

/// 有向三角形與圓的覆蓋面積, po為圓心
db Cover_circle(Point pa, Point pb, Point po, db r) {
    db a, b, c, x, y;
    db area = Cross(po, pa, pb)/2;
    a = Dist(po, pb);
    b = Dist(po, pa);
    c = Dist(pa, pb);
    /// 1、使用斯特瓦爾特定理
    /// 2、asin 的值域是 [-PI/2, PI/2]
    /// 情況一: 整個三角形在圓內
    if(sgn(a-r) <= 0 && sgn(b-r) <= 0) return area;
    /// 情況二: a 點在內, b 點在外
    else if(sgn(a-r) < 0 && sgn(b-r) >= 0) {
        x = (Dot(pa-pb, po-pb) + mysqrt(c*c*r*r-Cross(pb, pa, po)*Cross(pb, pa, po))) / c;
        return asin(area*(c-x)*2/c/b/r)*r*r/2 + area*x/c;
    }
    /// 情況三: b 點在內, a 點在外
    else if(sgn(a-r) >= 0 && sgn(b-r) < 0) {
        y = (Dot(pb-pa, po-pa) + mysqrt(c*c*r*r-Cross(pa, pb, po)*Cross(pa, pb, po))) / c;
        return asin(area*(c-y)*2/c/a/r)*r*r/2 + area*y/c;
    }
    /// 情況四: ab 線段與圓無交點
    else if(sgn(fabs(2*area)-r*c) >= 0 || sgn(Dot(pb-pa, po-pa)) <= 0 || sgn(Dot(pa-pb, po-pb)) <= 0) {
        if(sgn(Dot(pa-po, pb-po)) < 0) {
            if(sgn(Cross(po, pa, pb)) < 0) return (-PI-asin(area*2/a/b)) * r*r/2;
            return (PI-asin(area*2/a/b)) * r*r/2;
        }
        return asin(area*2/a/b) * r*r/2;
    }
    /// 情況五: 點 a 和點 b 都在外, 但線段 ab 與圓交點
    else {
        x = (Dot(pa-pb, po-pb) + mysqrt(c*c*r*r-Cross(pb, pa, po)*Cross(pb, pa, po))) / c;
        y = (Dot(pb-pa, po-pa) + mysqrt(c*c*r*r-Cross(pa, pb, po)*Cross(pa, pb, po))) / c;
        return (asin(area*(1-x/c)*2/r/b) + asin(area*(1-y/c)*2/r/a)) * r*r/2 + area*((y+x)/c-1);
    }
}
4. 圓與多邊形的面積交

測試: 牛客20324 J題, HDU3982, POJ3675

bool CPI_Area(Point *p, int n, Circle c) {
    db res = 0;
    p[n] = p[0];
    for (int i = 0; i < n; i ++) 
        res += Cover_circle(p[i], p[i+1], c.p, c.r);
    return fabs(res);
}

六. 與圓相關的演算法

三點 \(Simpson\) 公式: 用 \(g(x)=Ax^2+B+C\) 來擬合 \(f(x)\) , 則有:

\[\int_{a}^{b} f(x)dx\approx \int_{a}^{b}Ax^2+Bx+C=\frac{b-a}{6}[g(a)+4g(\frac{a+b}{2})+g(b)] \]
db f(db x) { 
    /// 某個函式表示式
}
db simpson(db l, db r) {
    db c = l + (r - l) / 2;
    return (f(l) + 4*f(c) + f(r)) * (r - l) / 6;
}
db asr(db l, db r, db tot, db eps) {
    db c = l + (r - l) / 2;
    db ls = simpson(l, c);
    db rs = simpson(c, r);
    if (fabs(ls + rs - tot) <= 15 * eps)    // 判斷精度
        return ls + rs + (ls + rs - tot) / 15.0;
    return asr(l, c, ls, eps/2) + asr(c, r, rs, eps/2);
}
db asr(db l, db r, db eps) {
    return asr(l, r, simpson(l, r), eps);
}
1. 最小圓覆蓋

測試: HDU - 3007

  • 幾何法, 時間複雜度接近 \(O(n)\) , 最壞達到 \(O(n^3)\)
Circle min_cover_circle(Point *p, int n) {
    random_shuffle(p, p + n);               	// 隨機函式, 打亂所有的點, 這一步很重要
    Circle c(p[0], 0);                      	// 從第一個點p[0]開始, 圓心為p[0], 半徑為0
    for (int i = 1; i < n; i ++)            	// 擴充套件所有的點
        if (sgn(Dist(p[i], c.p) - c.r) > 0) {	// 點p[i]在圓的外部
            c.p = p[i], c.r = 0;            	// 重新設定圓心為p[i], 半徑為0
            for (int j = 0; j < i; j ++)    	// 重新檢查之前所有點
                if (sgn(Dist(p[j], c.p) - c.r) > 0) {// 兩點定圓
                    c.p = (p[i] + p[j]) / 2;
                    c.r = Dist(p[i], c.p);
                    for (int k = 0; k < j; k ++) 
                        if (sgn(Dist(p[k], c.p) - c.r) > 0) { // 三點定圓
                            c.p = Circle_center(p[i], p[j], p[k]);
                            c.r = Dist(p[i], c.p);
                        }
                }
        }
}
  • 模擬退火法, 時間複雜度偏高
Circle min_cover_circle(Point *p, int n) {
    db T = 100.0; 		// 初始溫度的選擇與座標的值域有關
    db delta = 0.98; 	// 降溫係數
    Circle c(p[0], 0); 		// 設為初始解
    int pos = 0; 			// 找到距離圓心最遠的點
    while (T > eps) {
        c.r = pos = 0;
        for (int i = 0; i < n; i ++) {    // 尋找距離圓心最遠的點
            if (Dist(c.p, p[i]) > c.r) {
                c.r = Dist(c.p, p[i]);    // 距離圓心最遠的點肯定在圓上
                pos = i;
            }
        }
        c.p = c.p + (p[pos] - c.p) / c.r * T;  // 逼近最後的解
        T *= delta;
    }
    return c;
}
2. 多圓的面積並

測試: SPOJ CIRU, CIRU

  • 幾何法: 時間複雜度: \(O(n^2)\)
struct node {
    Point p;
    db ang;
    int id;
    node(Point a=Point(0,0), db b=0, int c=0):p(a), ang(b), id(c) {}
    bool operator < (const node& rhs) const {
        return sgn(ang - rhs.ang) < 0; 
    }
};
db arg(Point a) {
    return atan2(a.y, a.x);
}
db solve() {
    int cnt = 0;
    sort (c, c + n);
    n = unique(c, c + n) - c;
    for (int i = n - 1; i >= 0; i --) {
        bool flag = true;
        for (int j = i + 1; j < n; j ++) {
            db d = (c[i].p - c[j].p).len();
            if (sgn(d - abs(c[i].r-c[j].r)) <= 0) {
                flag = false; break; //包含
            }
        }
        if (flag) arr[cnt ++] = c[i];
    }
    db ans = 0;
    for (int i = 0; i < cnt; i ++) {
        vector<node> vec;
        Point bound(arr[i].p.x-arr[i].r, arr[i].p.y);
        vec.push_back(node(bound, -PI, 0));
        vec.push_back(node(bound, PI, 0));
        for (int j = 0; j < cnt; j ++) {
            if (j == i) continue;
            db d = (arr[i].p - arr[j].p).len();
            if (sgn(d - (arr[i].r + arr[j].r)) < 0) {
                pair<Point, Point> ret = Crosspoint(arr[i], arr[j]);
                db x = arg(ret.first-arr[i].p);
                db y = arg(ret.second-arr[i].p);
                if (sgn(x - y) > 0) {
                    vec.push_back(node(ret.first, x, 1));
                    vec.push_back(node(bound, PI, -1));
                    vec.push_back(node(bound, -PI, 1));
                    vec.push_back(node(ret.second, y, -1));
                }else {
                    vec.push_back(node(ret.first, x, 1));
                    vec.push_back(node(ret.second, y, -1));
                }
            }
        }
        sort (vec.begin(), vec.end());
        int sum = vec[0].id;
        for (int j = 1; j < (int)vec.size(); j ++) {
            if (sum == 0) {
                ans += Cross(vec[j-1].p, vec[j].p) / 2;
                db x = vec[j-1].ang;
                db y = vec[j].ang;
                db area = arr[i].r * arr[i].r * (y - x) / 2;
                Point v1 = vec[j-1].p - arr[i].p;
                Point v2 = vec[j].p - arr[i].p;
                area -= Cross(v1, v2) / 2;
                ans += area;
            }
            sum += vec[j].id;
        }
    }
    return ans;
}
  • 積分法: 時間複雜度均攤: \(O(nlog\ 定義域 + n^2)\) , 不穩定, 取決於精度, 但相對好寫
struct Circle {
    db x, y, r, L, R;
    bool operator < (const Circle &rhs) const {
        return sgn(L - rhs.L) < 0;
    }
}c[N];
vector<Circle> vec;
map<db, db> mp;
db ans = 0;
vector<pair<db, db> > st;

db f(db x) {
    if (mp.count(x)) return mp[x];
    st.clear();
    for (int i = 0; i < (int)vec.size(); i ++) {
        if (sgn(fabs(vec[i].x - x) - vec[i].r) < 0) {
            db d = vec[i].x - x, u = mysqrt(sqr(vec[i].r) - sqr(d));
            st.push_back(make_pair(vec[i].y-u, vec[i].y+u));
        }
    }
    sort (st.begin(), st.end());
    db mx = -inf, ret = 0;
    for (auto it = st.begin(); it != st.end(); it ++) {
        mx = max(mx, it->first);
        ret += max(0.0, it->second - mx);
        mx = max(mx, it->second);
    }
    return mp[x] = ret;
}

db simpson(db l, db r, db Fa, db Fc) {
    db Fb = f((l+r)/2);
    return (Fa + 4 * Fb + Fc) / 6 * (r - l);
}

db asr(db l, db r, db tot) {
    db mid = (l + r) / 2;
    db fmi = f(mid), fl = f(l), fr = f(r);
    db ls = simpson(l, mid, fl, fmi);
    db rs = simpson(mid, r, fmi, fr);
    if (fabs(tot - ls - rs) < 1e-6) return ls + rs;
    return asr(l, mid, ls) + asr(mid, r, rs);
}
db asr(db l, db r) {
    return asr(l, r, simpson(l, r, f(l), f(r)));
}
bool vis[N]; int n;

db solve(Circle* c, int n) {
    sort (c, c + n);
    db mx = -inf, pre;
    for (int i = 0; i < n; i ++) {
        for (int j = 0; j < n; j ++) {
            if (i == j || vis[j]) continue;
            db d = mysqrt(sqr(c[i].x-c[j].x)+sqr(c[i].y-c[j].y));
            if (sgn(d + c[i].r - c[j].r) <= 0) {
                vis[i] = 1; break;
            } 
        }
    }
    for (int i = 0; i < n; i ++) {
        if (vis[i]) continue;
        if (c[i].L > mx) {
            if (vec.size()) {
                ans += asr(pre, mx);
                vec.clear();
                mp.clear();
            }
        }
        if (vec.empty()) pre = c[i].L;
        vec.push_back(c[i]);
        mx = max(c[i].R, mx);
    }
    if (vec.size()) ans += asr(pre, mx);
    return ans;
}