計算幾何
Ian Wen
座標與向量
有向面積
線段相交
點到線段距離
極角排序
凸包演算法 (Monotone Chain)
計算幾何模板 (今天教的所有東西都在裡面)
#define X first
#define Y second
#define eps 1e-12
// #define double long long
typedef pair<double, double> pdd;
pdd operator+(pdd a, pdd b)
{ return pdd(a.X + b.X, a.Y + b.Y); }
pdd operator-(pdd a, pdd b)
{ return pdd(a.X - b.X, a.Y - b.Y); }
pdd operator*(pdd a, double b)
{ return pdd(a.X*b, a.Y*b); }
pdd operator/(pdd a, double b)
{ return pdd(a.X/b, a.Y/b); }
inline bool operator == (const pdd &a, const pdd &b){
if(abs(a.X-b.X) <= eps && abs(a.Y - b.Y) <= eps) return true;
return false;
}
double dot(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 abs2(pdd a) // Square
{ return dot(a, a); }
double abs(pdd a)
{ return sqrt(dot(a, a)); }
int sign(double a)
{ return fabs(a) < eps ? 0 : a > 0 ? 1 : -1; }
int ori(pdd a, pdd b, pdd c)
{ return sign(cross(b - a, c - a)); }
bool collinearity(pdd p1, pdd p2, pdd p3) { // 三點共線
return sign(cross(p1 - p3, p2 - p3)) == 0;
}
bool btw(pdd p1, pdd p2, pdd p3) { // p3 是否在 p1 p2 線段上
if (!collinearity(p1, p2, p3)) { return 0; }
else { return sign(dot(p1 - p3, p2 - p3)) <= 0; }
}
bool banana(pdd p1, pdd p2, pdd p3, pdd p4) {
int a123 = ori(p1, p2, p3);
int a124 = ori(p1, p2, p4);
int a341 = ori(p3, p4, p1);
int a342 = ori(p3, p4, p2);
if (btw(p1, p2, p3) || btw(p1, p2, p4) || btw(p3, p4 ,p1) || btw(p3, p4, p2)) {
return true;
}
else {
return a123 * a124 < 0 && a341 * a342 < 0;
}
}
pdd banana_point(pdd p1, pdd p2, pdd p3, pdd p4) {
double a123 = cross(p2 - p1, p3 - p1);
double a124 = cross(p2 - p1, p4 - p1);
return (p4 * a123 - p3 * a124) / (a123 - a124);
}
int angle_type(pdd a, pdd o, pdd b) {
double d = dot(a - o, b - o);
return sign(d); // 0: 直角, >0: 銳角, <0: 鈍角
}
double pt_to_seg_dist(pdd a, pdd b, pdd k) {
if (angle_type(a, b, k) > 0 && angle_type(b, a, k) > 0) {
return fabs(cross(a - k, b - k)) / abs(b - a); // 投影在中間,算高
}
return sqrt(min(abs2(a - k), abs2(b - k))); // 投影不在線段上,取端點距離
}
pdd base;
bool angle_cmp(pdd a, pdd b) {
a = a - base;
b = b - base;
bool f1 = (a.Y < 0 || (a.Y == 0 && a.X < 0)); // 判斷在 y < 0 的半平面
bool f2 = (b.Y < 0 || (b.Y == 0 && b.X < 0));
if (f1 != f2) return f1 < f2;
return cross(a, b) > 0; // 逆時針順序
}
void angle_sort(vector<pdd>& pts, pdd origin) {
base = origin;
sort(pts.begin(), pts.end(), angle_cmp);
}
vector<pdd> convex_hull(vector<pdd> pts) {
sort(pts.begin(), pts.end());
vector<pdd> hull;
for (int t=0; t<2; t++) { // 找下找上
int sz = hull.size();
for (pdd pt : pts) {
while (hull.size() - sz >= 2 && ori(hull[hull.size()-2], hull.back(), pt) < 0) { // <= 0 忽略邊上的點 < 0 不忽略
hull.pop_back(); // 單調維護
}
hull.push_back(pt);
}
hull.pop_back(); // 下凸包最後一點是上凸包的開頭
reverse(pts.begin(), pts.end());
}
return hull;
}
座標
向量
向量運算
EPS
座標與向量
相信各位已經讀過課前預習了
座標與向量
直角坐標系
\((x,\, y)\)
極坐標系
\((r,\, \theta)\)
座標與向量
\(\left|\overrightarrow{AB}\right| = \sqrt{{(x_A - x_B)}^2 + {(y_A - y_B)}^2}\)
座標與向量
座標與向量
座標與向量
加上反向向量
\(\overrightarrow{AB} - \overrightarrow{CD} = \overrightarrow{AB} + \overrightarrow{DC}\)
座標與向量
分為三種
座標與向量
分為三種
座標與向量
若 \(\vec A = (x, y)\)
\(k\) 為純量
則 \(k\vec A = (kx, ky)\)
幾何意義:
將向量長度放大 k 倍
座標與向量
定義:若 \(\vec a\) 與 \(\vec b\) 為二度或三度空間的向量,且 \(\theta\) 為 \(\vec a\) 與 \(\vec b\) 的夾角(滿足\(0\leq\theta\leq\pi\)),
則點積 \(\vec a\cdot\vec b\) 定義為
座標與向量
座標與向量
座標與向量
定義:若\(a = (a_1,a_2,a_3)\)與\(b = (b_1,b_2,b_3)\)為三度空間裡的向量,則外積(叉積)\(a\times b\) 為一向量定義為:
向量的外積只有在三度空間才有定義!
外積結果的向量由右手定則決定
座標與向量
座標與向量
座標與向量
座標與向量
座標也可以用一樣的方式實作,例如:
\(A\)點的座標可以視為\(\overrightarrow{OA}\) 來處理
#define X first
#define Y second
#define eps 1e-12
// #define double long long
typedef pair<double, double> pdd;
pdd operator+(pdd a, pdd b)
{ return pdd(a.X + b.X, a.Y + b.Y); }
pdd operator-(pdd a, pdd b)
{ return pdd(a.X - b.X, a.Y - b.Y); }
pdd operator*(pdd a, double b)
{ return pdd(a.X*b, a.Y*b); }
pdd operator/(pdd a, double b)
{ return pdd(a.X/b, a.Y/b); }
inline bool operator == (const pdd &a, const pdd &b){
if(abs(a.X-b.X) <= eps && abs(a.Y - b.Y) <= eps) return true;
return false;
}
double dot(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 abs2(pdd a) // Square
{ return dot(a, a); }
double abs(pdd a)
{ return sqrt(dot(a, a)); }
座標與向量
\(x ⇒ (x − eps, x + eps)\)
inline bool operator == (const pdd &a, const pdd &b){
if(abs(a.X-b.X) <= eps && abs(a.Y - b.Y) <= eps) return true;
return false;
}
性質
Pick's Theorem
題目
有向面積
+
-
有向面積
計算面積
評估角度
有向面積
兩向量夾出的三角形有向面積為:
A
B
C
有向面積
多邊形面積
Source: https://reurl.cc/NZ5ZNq
有向面積
多邊形面積
假設多邊形的頂點依序為(要特別注意結束的點必須是起始點):
則多邊形的面積為(aka測量師公式):
有向面積
多邊形面積
有向面積
Pick's Theorem
給定頂點座標均是格子點的簡單多邊形,皮克定理說明了其面積\(A\)和內部格點數目\(i\)、邊上格點數目\(b\)的關係:
證明在這裡
有向面積
Pick's Theorem - 舉例
有向面積
Pick's Theorem - 舉例
有向面積
題目
如何判斷
方向函式
點與線段的關係
蕉點座標
線段香蕉應用
banana
二元一次方程式求交點
看有沒有解
更快的做法?
直線法向量
若一直線 \(ax+by = c\)
則其法向量為 (a, b)
法向量方向與直線垂直
proof ? 畫看看
已知兩直線法向量,
可透過法向量是否平行得知兩向量有沒有焦點
如何判斷兩個直線是否香蕉?
如何判斷兩個線段是否香蕉?
直接求出直線交點,再判斷交點是否在線段內
這樣好嗎?
聽起來很麻煩誒
求交點的問題
banana
//方向函數ori,回傳ab向量與ac向量的方向
int sign(double a)
{ return fabs(a) < eps ? 0 : a > 0 ? 1 : -1; }
int ori(pdd a, pdd b, pdd c)
{ return sign(cross(b - a, c - a)); }
banana
方向函式能做什麼?
判斷點是否在線段兩端點的異側!
banana
若線段 \(AB\) 與線段 \(CD\) 相交
則點 \(C\) 和 \(D\) 會在線段 \(AB\) 異側
且 點 \(A\) 和 \(B\) 會在線段 \(CD\) 異側
=0代表C或D有一點在直線 AB 上面
banana
線段端點在另一線段上
btw()
判斷點 O 是否在 AB 之間bool btw(pdd p1, pdd p2, pdd p3) { // p3 是否在 p1 p2 線段上
if (!collinearity(p1, p2, p3)) { return 0; }
else { return sign(dot(p1 - p3, p2 - p3)) <= 0; }
}
banana
bool banana(pdd p1, pdd p2, pdd p3, pdd p4) {
int a123 = ori(p1, p2, p3);
int a124 = ori(p1, p2, p4);
int a341 = ori(p3, p4, p1);
int a342 = ori(p3, p4, p2);
if (btw(p1, p2, p3) || btw(p1, p2, p4) || btw(p3, p4 ,p1) || btw(p3, p4, p2)) {
return true;
}
else {
return a123 * a124 < 0 && a341 * a342 < 0;
}
}
banana
知道兩線段是否相交之後,進一步求出交點座標
banana
幾個算式列一列得到:
帶回 \(\vec{P} = \vec{A} + t\overrightarrow{AB}\) 即可
pdd banana_point(pdd p1, pdd p2, pdd p3, pdd p4) {
double a123 = cross(p2 - p1, p3 - p1);
double a124 = cross(p2 - p1, p4 - p1);
return (p4 * a123 - p3 * a124) / (a123 - a124);
}
banana
給定平面上\(n\)個點構成的簡單多邊形,還有\(m\)個詢問的點。對每一個點輸出它是在多邊形內部、外部或邊界上。
$$3\leq n\leq 1000$$
$$1\leq m\leq 1000$$
$$10^9\leq x_i,y_i\leq 10^9$$
banana
banana
banana
banana
問 C 點到 \(\overline{\rm AB}\) 的距離平方
Case - 1
垂足在外面
\(\min(\rm |C-A|, |C-B|)\)
Case - 2
垂足在線段上
用面積以及底邊長度求高!
所以到底怎麼判垂足在哪?
\(\angle CAB\) 和 \(\angle CBA\) 都是銳角時,長度為高!
銳角? 用內積!
實作
int angle_type(pdd a, pdd o, pdd b) {
double d = dot(a - o, b - o);
return sign(d); // 0: 直角, >0: 銳角, <0: 鈍角
}
double pt_to_seg_dist(pdd a, pdd b, pdd k) {
if (angle_type(a, b, k) > 0 && angle_type(b, a, k) > 0) {
return fabs(cross(a - k, b - k)) / abs(b - a); // 投影在中間,算高
}
return sqrt(min(abs2(a - k), abs2(b - k))); // 投影不在線段上,取端點距離
}
利用每一個對特定點(通常是原點)的極座標的角度進行排序
pdd base;
bool angle_cmp(pdd a, pdd b) {
a = a - base;
b = b - base;
bool f1 = (a.Y < 0 || (a.Y == 0 && a.X < 0)); // 判斷在 y < 0 的半平面
bool f2 = (b.Y < 0 || (b.Y == 0 && b.X < 0));
if (f1 != f2) return f1 < f2;
return cross(a, b) > 0; // 逆時針順序
}
void angle_sort(vector<pdd>& pts, pdd origin) {
base = origin;
sort(pts.begin(), pts.end(), angle_cmp);
}
#pragma GCC optimize("Ofast")
#pragma GCC optimize("fast-math","unroll-loops","no-stack-protector")
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define ff first
#define ss second
#define rep(i, n) for(signed i=0; i<signed(n); i++)
#define all(x) x.begin(),x.end()
#define int long long
const int MOD = 1e9+7;
const int MAXN = 3e5+5;
const int PRIME = 712271227;
const int INF = 1000000005;
const long long INFF = 2e18;
#define X first
#define Y second
#define eps 1e-12
typedef pair<double, double> pdd;
pdd operator+(pdd a, pdd b) {
return pdd(a.X + b.X, a.Y + b.Y);
}
pdd operator-(pdd a, pdd b) {
return pdd(a.X - b.X, a.Y - b.Y);
}
pdd operator*(pdd a, double b) {
return pdd(a.X * b, a.Y * b);
}
pdd operator/(pdd a, double b) {
return pdd(a.X / b, a.Y / b);
}
inline bool operator==(const pdd &a, const pdd &b){
return (fabs(a.X - b.X) <= eps && fabs(a.Y - b.Y) <= eps);
}
// 向量內積與外積
double dot(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 abs2(pdd a){ return dot(a, a); }
double abs(pdd a){ return sqrt(dot(a, a)); }
int sign(double a){
return fabs(a) < eps ? 0 : (a > 0 ? 1 : -1);
}
// 幾何判斷:三點的定向
int ori(pdd a, pdd b, pdd c){
return sign(cross(b - a, c - a));
}
// 以下函式在本題中未被使用,可以保留作其他用途
bool collinearity(pdd p1, pdd p2, pdd p3) {
return sign(cross(p1 - p3, p2 - p3)) == 0;
}
bool btw(pdd p1, pdd p2, pdd p3) {
if (!collinearity(p1, p2, p3)) return false;
return sign(dot(p1 - p3, p2 - p3)) <= 0;
}
bool banana(pdd p1, pdd p2, pdd p3, pdd p4) {
int a123 = ori(p1, p2, p3);
int a124 = ori(p1, p2, p4);
int a341 = ori(p3, p4, p1);
int a342 = ori(p3, p4, p2);
if (btw(p1, p2, p3) || btw(p1, p2, p4) || btw(p3, p4, p1) || btw(p3, p4, p2)) {
return true;
} else {
return a123 * a124 < 0 && a341 * a342 < 0;
}
}
pdd banana_point(pdd p1, pdd p2, pdd p3, pdd p4) {
double a123 = cross(p2 - p1, p3 - p1);
double a124 = cross(p2 - p1, p4 - p1);
return (p4 * a123 - p3 * a124) / (a123 - a124);
}
int angle_type(pdd a, pdd o, pdd b) {
double d = dot(a - o, b - o);
return sign(d); // 0: 直角, >0: 銳角, <0: 鈍角
}
double pt_to_seg_dist(pdd a, pdd b, pdd k) {
if (angle_type(a, b, k) > 0 && angle_type(b, a, k) > 0) {
return fabs(cross(a - k, b - k)) / abs(b - a);
}
return sqrt(min(abs2(a - k), abs2(b - k)));
}
pdd base;
bool angle_cmp(pdd a, pdd b) {
a = a - base;
b = b - base;
bool f1 = (a.Y < 0 || (a.Y == 0 && a.X < 0)); // 判斷在 y < 0 的半平面
bool f2 = (b.Y < 0 || (b.Y == 0 && b.X < 0));
if (f1 != f2) return f1 < f2;
return cross(a, b) > 0; // 逆時針順序
}
void angle_sort(vector<pdd>& pts, pdd origin) {
base = origin;
sort(pts.begin(), pts.end(), angle_cmp);
}
// 想法:以某點 id 為直角頂點,統計構成直角三角形的組數
int solve(pdd id, const vector<pdd>& pts) {
vector<pdd> temp; // 存放以 id 為原點的向量集合
vector<int> cnt; // 存放同一方向上重合的點數量
vector<pdd> pp; // 壓縮共線後的唯一方向向量集合
// 排除點 id 自身,其餘點轉換為以 id 為原點
for (auto p : pts) {
pdd cur = p - id;
if(cur == pdd(0, 0)) continue;
temp.push_back(cur);
}
// 依極角排序
angle_sort(temp, pdd(0, 0));
// 壓縮共線方向,只保留每個方向的一個向量,並記錄其重合數量
pp.push_back(temp[0]);
cnt.push_back(1);
int len = temp.size();
for (int i = 1; i < len; i++){
double c = cross(temp[i], temp[i - 1]);
double d = dot(temp[i], temp[i - 1]);
// 如果兩向量共線且同向則合併計數
if (fabs(c) <= eps && d >= -eps)
cnt.back()++;
else {
pp.push_back(temp[i]);
cnt.push_back(1);
}
}
len = pp.size();
// 為了處理極角跨圈的情況,將 pp 與 cnt 再複製一遍接在後面
for (int i = 0; i < len; i++){
pp.push_back(pp[i]);
cnt.push_back(cnt[i]);
}
int ans = 0, p1 = 0;
// 雙指針枚舉每個方向向量 pp[i]
for (int i = 0; i < len; i++){
// 以 pp[i] 為一邊,找另一邊與其垂直,即內積等於 0 且夾角為正向(外積 > 0)的方向
while(p1 < i + len && sign(cross(pp[i], pp[p1])) >= 0 && dot(pp[i], pp[p1]) > eps)
p1++;
// 當找到滿足垂直條件的向量時,累計組合數量
if(p1 < i + len && sign(cross(pp[i], pp[p1])) > 0 && fabs(dot(pp[i], pp[p1])) <= eps)
ans += cnt[i] * cnt[p1];
}
return ans;
}
// #define FASTIO
// #define AUTOIO
signed main() {
#ifdef FASTIO
ios::sync_with_stdio(0);cin.tie(0);
#endif
#ifdef AUTOIO
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
int n;
while(cin >> n && n) {
vector<pdd> pts(n);
for (int i = 0; i < n; i++){
cin >> pts[i].X >> pts[i].Y;
}
int answer = 0;
// 遍歷所有點,統計以該點為直角頂點的直角三角形個數
for (int i = 0; i < n; i++){
answer += solve(pts[i], pts);
}
cout << answer << "\n";
}
return 0;
}
\(O(n^2\log n)\)
可以試著寫看看 \(O(n^3)\) 的做法
找凸包的演算法有很多
找凸包的演算法有很多
凸包?
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
把下半部凸包圍起來,
上半部也可以照同樣方式圍出來。
4. 對點逆序之後把上凸包圍出來
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
將所有點reverse圍上凸包!
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
單調鍊凸包
Monotone Chain
好欸,做完了!
單調鍊凸包
Monotone Chain
好欸,做完了!
單調鍊凸包
Monotone Chain
vector<pdd> convex_hull(vector<pdd> pts) {
sort(pts.begin(), pts.end());
vector<pdd> hull;
for (int t=0; t<2; t++) { // 找下再找上
int sz = hull.size();
for (pdd pt : pts) {
while (hull.size() - sz >= 2 && ori(hull[hull.size()-2], hull.back(), pt) < 0) { // <= 0 忽略邊上的點 < 0 不忽略
hull.pop_back(); // 單調維護
}
hull.push_back(pt);
}
hull.pop_back(); // 下凸包最後一點是上凸包的開頭
reverse(pts.begin(), pts.end());
}
return hull;
}
單調鍊凸包
Monotone Chain
段考即將炸裂的
Ian Wen