AaW
座標與向量
有向面積
線段相交
極角排序
凸包演算法
「計算幾何是以數學方式精確與高效的計算出幾何結構與其性質的嚴謹應用。」
直角坐標系
\((x,\, y)\)
極坐標系
\((r,\, \theta)\)
\(\left|\overrightarrow{AB}\right| = \sqrt{{(x_A - x_B)}^2 + {(y_A - y_B)}^2}\)
struct pt {
T X, Y; // T 可以為 int 或是 double
}
#define pt pair<int, int>
#define X first
#define Y second
pt v1;
cin >> v1.X >> v1.Y;
或是
座標也可以用一樣的方式實作,例如:
\(A\)點的座標可以視為\(\overrightarrow{OA}\) 來處理
向量的運算
向量加法
向量減法
終點減起點
加上反向向量
Ex. \(\overrightarrow{AB} - \overrightarrow{CD} = \overrightarrow{AB} + \overrightarrow{DC}\)
定義:若 \(\vec a\) 與 \(\vec b\) 為二度或三度空間的向量,且 \(\theta\) 為\(\vec a\) 與 \(\vec b\) 的夾角(滿足\(0\leq\theta\leq\pi\)),
則點積 \(\vec a\cdot\vec b\) 定義為
Dot Product
Source: Wiki
Dot Product
等等會一直用到
Cross product
定義:若\(a = (a_1,a_2,a_3)\)與\(b = (b_1,b_2,b_3)\)為三度空間裡的向量,則外積(叉積)\(a\times b\) 為一向量定義為:
向量的外積只有在三度空間才有定義!
外積結果的向量由右手定則決定
Cross product
Source: Wiki
Source: Wiki
Cross product
用運算子多載
struct pt{
int x,y;
};
pt operator+(const pt &a, const pt &b) {
return {a.x + b.x, a.y + b.y};
} //向量相加
pt operator-(const pt &a, const pt &b) {
return {a.x - b.x, a.y - b.y};
} //向量相減
pt operator*(int k, const pt &a) {
return {a.x * k, a.y * k};
} //向量乘法(係數積)
pt operator*(const pt &a, int k) {
return {a.x * k, a.y * k};
} // 乘法有交換率
pt operator/(const pt &a, int k) {
return {a.x / k, a.y / k};
} //向量除法 (係數除法)
int operator^(const pt &a, const pt &b) {
return a.x * b.y - a.y * b.x;
} //向量外積cross
int operator*(const pt &a, const pt &b) {
return a.x * b.x + a.y * b.y;
} //向量內積dot
inline bool operator == (const pt &a, const pt &b){
if(abs(a.x-b.x) <= eps && abs(a.y - b.y) <= eps) return true;
return false;
}
\(x ⇒ (x − eps, x + eps)\)
Source: https://pse.is/3suhzc
Source: Wiki
+
-
兩向量夾出的三角形有向面積為:
A
B
C
WHY?
幾何證明:簡單明瞭
Source: https://reurl.cc/NZ5ZNq
假設多邊形的頂點依序為(要特別注意結束的點必須是起始點):
則多邊形的面積為(aka測量師公式):
給定頂點座標均是格子點的簡單多邊形,皮克定理說明了其面積\(A\)和內部格點數目\(i\)、邊上格點數目\(b\)的關係:
證明在這裡
Source: Wiki
segment banana
直接求出交點,再判斷交點是否在線段內
如何判斷?
這樣好嗎?
聽起來很麻煩誒
求交點的問題
//方向函數ori,回傳OA向量與OB向量的方向
int ori( const Pt& a , const Pt& b, const Pt& o){ //方向函數
double cross = ( a - o ) ^ ( b - o );
if( fabs( cross ) < eps ) return 0;
return cross > 0 ? 1 : -1;
}
方向函式能做什麼?
判斷點是否在線段兩端點的異側!
若線段\(AB\) 與\(CD\) 相交
則點\(C\)和\(D\)會在線段\(AB\)異側
=0代表C或D有一點在直線 AB 上面
bool onseg(pt a, pt b, pt o){ //o是否在ab線段上
int cross = (a - o) ^ (b - o); //是否平行
int dot = (a - o) * (b - o); //是否在線段中
return (cross == 0)&&(dot <= 0);
}
bool banana(pt a, pt b, pt c, pt d){ //線段ab是否與cd相交
if(onseg(a,b,c)||onseg(a,b,d)) return true; //點c、d是否洽在線段ab上
if(onseg(c,d,a)||onseg(c,d,b)) return true; //點a、b是否洽在線段cd上
return (ori(a,b,c)*ori(a,b,d) < 0 && ori(c,d,a)*ori(c,d,b) < 0);
}
資芽版的香蕉
知道兩線段是否相交之後,進一步求出交點座標
其中,\(t\)為ACD和
知道兩線段是否相交之後,進一步求出交點座標
幾個算式列一列得到:
帶回 \(\vec{P} = \vec{A} + t\overrightarrow{AB}\) 即可
pt banana_point(pt a, pt b, pt c, pt d){
assert(banana(a, b, c, d)); //是否相交
return a + ((a - c)^(d - c) * (b - a))/((d - c)^(b - a));
}
給定平面上\(n\)個點構成的簡單多邊形,還有\(m\)個詢問的點。對每一個點輸出它是在多邊形內部、外部或邊界上。
$$3\leq n\leq 1000$$
$$1\leq m\leq 1000$$
$$10^9\leq x_i,y_i\leq 10^9$$
Source: geeksforgeeks
#include <bits/stdc++.h>
#define int long long
#define double long double
#define pii pair<int, int>
#define N 1005
#define INF 1000000005
#define x first
#define y second
#define IOS ios::sync_with_stdio(0),cin.tie(0)
using namespace std;
int n,m;
struct point{
int x,y;
bool operator == (point b){
if(x == b.x && y == b.y)return true;
return false;
}
point operator - (point b){return {x - b.x , y - b.y};}
point operator + (point b){return {x + b.x , y + b.y};}
int operator ^ (point b){return (x * b.y - y * b.x);}
int operator * (point b){return (x * b.x + y * b.y);}
}pt[N];
bool onseg(point a,point b,point o){
int cross = (a - o) ^ (b - o);
int dot = (a - o) * (b - o);
return (cross == 0) && (dot <= 0);
}
int dir(point a,point b,point o){
int cross = (a - o) ^ (b - o);
if(cross == 0)return 0;
else if(cross > 0)return 1;
else return -1;
}
bool inter(point a,point b,point c,point d){
if(onseg(a,b,c) || onseg(a,b,d))return true;
if(onseg(c,d,a) || onseg(c,d,b))return true;
if(dir(a,b,c) * dir(a,b,d) < 0 && dir(c,d,a) * dir(c,d,b) < 0)
return true;
return false;
}
int solve(point cur){
int sum = 0;
for(int i=0;i<n;i++){
if(onseg(pt[i],pt[i+1],cur) == 1)return -1;
if(inter(pt[i],pt[i+1],cur,point{INF,cur.y}))sum++;
point temp = pt[i].y > pt[i+1].y ? pt[i] : pt[i+1];
if(temp.y == cur.y && temp.x > cur.x)sum--;
}
return sum;
}
signed main(){
IOS;
cin>>n>>m;
for(int i=0;i<n;i++)cin>>pt[i].x>>pt[i].y;
pt[n] = pt[0];
for(int i=0;i<m;i++){
point temp;cin>>temp.x>>temp.y;
int ans = solve(temp);
if(ans == -1)cout<<"BOUNDARY"<<"\n";
else if(ans & 1)cout<<"INSIDE"<<"\n";
else cout<<"OUTSIDE"<<"\n";
}
}
問 C 點到 \(\overline{\rm AB}\) 的距離平方
case 1
垂足在外面
\(\min(\rm |C-A|, |C-B|)\)
case 2
垂足在線段上
用面積以及底邊長度求高!
所以到底怎麼判垂足在哪?
\(\angle CAB\) 和 \(\angle CBA\) 都是銳角時,長度為高!
用內積!
資芽給的實作
逆時鐘一個一個排
利用每一個對特定點(通常是原點)的極座標的角度進行排序
Source: Wiki
bool cmp(pt a, pt b){
return atan2(a.y,a.x) < atan2(b.y,b.x);
}
sort(p.begin(),p.end(),cmp);
bool cmp(pt a, pt b){
bool f1 = a < pt{0,0};
bool f2 = b < pt{0,0};
if(f1 != f2)return f1 < f2;
return (a ^ b) > 0;
//逆時針將點進行極角排序,從270度開始逆時針
}
sort(p.begin(),p.end(),cmp);
//以id為原點進行極角排序
給你 \(N\) 個座標平面上的點\((x_i,y_i)\),請問總共可形成多少個直角三角形呢?
\(3\leq N \leq 1500, -10^9\leq x_i,y_i\leq 10^9\)
註:保證同一座標不會有兩個點
#include <bits/stdc++.h>
#define Orz ios::sync_with_stdio(0),cin.tie(0)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pii pair<int,int>
#define pdd pair<double,double>
#define int long long
#define ll long long
#define ld long double
#define N 100001
#define all(x) x.begin(),x.end()
#define eps 1e-9
#define x first
#define y second
using namespace std;
struct pt{
int x,y;
bool operator < (pt b){
if(x == b.x)return y < b.y;
return x < b.x;
}
bool operator > (pt b){
if(x == b.x)return y > b.y;
return x > b.x;
}
bool operator == (pt b){
if(x-b.x == 0 && y-b.y == 0)return true;
return false;
}
pt operator+(pt b) {return {x + b.x, y + b.y};} //向量相加
pt operator-(pt b) {return {x - b.x, y - b.y};} //向量相減
int operator^(pt b) {return x * b.y - y * b.x;} //向量外積cross
int operator*(pt b) {return x * b.x + y * b.y;} //向量內積dot
};
vector<pt> p,temp,pp;
vector<int> cnt;
int n,ans = 0;
bool cmp(pt a, pt b){
bool f1 = a < pt{0,0};
bool f2 = b < pt{0,0};
if(f1 != f2)return f1 < f2;
return (a ^ b) > 0;
//逆時針將點進行極角排序,從270度開始逆時針
}
//O(n)枚舉每個點當直角情況
int solve(pt id){
pp.clear();cnt.clear();temp.clear();
for(pt i : p){
pt cur = i - id;
if(cur == pt{0,0})continue;
temp.push_back(cur);
}
sort(all(temp),cmp); //以id為原點進行極角排序
pp.push_back(temp[0]); //pp每一角度只存至多一個點
cnt.push_back(1); //考慮每個點共線情況
int len = temp.size();
rep(i,1,len-1){
int cross = temp[i]^temp[i-1],dot = temp[i]*temp[i-1];
if(cross == 0 && dot >= 0)cnt[cnt.size()-1] += 1; //共線數量+=1
else {pp.push_back(temp[i]);cnt.push_back(1);} //非共線設定數量為1
}
len = pp.size(); //考慮橫跨一周的情況
rep(i,0,len-1){ //雙指針i,p1可能會超過一圈
pp.push_back(pp[i]); //將點再繞一圈
cnt.push_back(cnt[i]);
}
int ans = 0,p1 = 0;
rep(i, 0, len-1){
while(p1 < i+len && (pp[i]^pp[p1]) >= 0 && (pp[i]*pp[p1]) > 0)p1 += 1;
//夾銳角的情況要p1+=1
if((pp[i]^pp[p1]) > 0 && (pp[i]*pp[p1]) == 0)ans += cnt[i]*cnt[p1];
//正向的直角三角形,若共線則兩者數量相乘
}
return ans;
}
signed main(){
Orz;
while(cin>>n){
if(n == 0)break;
p.assign(n,{0,0});
rep(i,0,n-1)cin>>p[i].x>>p[i].y;
int ans = 0;
rep(i,0,n-1){
ans += solve(p[i]);
}
cout<<ans<<endl;
}
}
給你二維平面上\(n\)個點(n≤2400),每一個點座標皆不相同,計算總共可以畫出多少個三角形?
$$-10^9\leq x_i,y_i\leq 10^9$$
#include <bits/stdc++.h>
#define Orz ios::sync_with_stdio(0),cin.tie(0)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pii pair<int,int>
#define pdd pair<double,double>
#define int long long
#define ll long long
#define ld long double
#define N 100001
#define all(x) x.begin(),x.end()
#define eps 1e-9
#define x first
#define y second
using namespace std;
struct pt{
int x,y;
bool operator < (pt b){
if(x == b.x)return y < b.y;
return x < b.x;
}
bool operator > (pt b){
if(x == b.x)return y > b.y;
return x > b.x;
}
bool operator == (pt b){
if(x-b.x == 0 && y-b.y == 0)return true;
return false;
}
pt operator+(pt b) {return {x + b.x, y + b.y};} //向量相加
pt operator-(pt b) {return {x - b.x, y - b.y};} //向量相減
int operator^(pt b) {return x * b.y - y * b.x;} //向量外積cross
int operator*(pt b) {return x * b.x + y * b.y;} //向量內積dot
};
vector<pt> p;
vector<int> cnt;
int n,ans = 0;
bool cmp(pt a, pt b){
bool f1 = a < pt{0,0};
bool f2 = b < pt{0,0};
if(f1 != f2)return f1 < f2;
return (a ^ b) > 0;
//逆時針將點進行極角排序,從270度開始逆時針
}
//用cnt[i]統計區間長度為i的線段數量
void solve(pt id){
vector<pt> pp;
for(auto i : p){ //以id為原點
pt cur = i-id;
if(cur == pt{0,0})continue;
if(cur.y < 0){cur.x = -cur.x;cur.y = -cur.y;}
if(cur.x < 0 && cur.y==0){cur.x = -cur.x;}
pp.push_back(cur);
}
sort(all(pp),cmp); //將id當作原點進行排序
int p1 = 0,p2 = 0,len = pp.size(); //雙指針找共線區間
while(p1 < n-1){ //最大化區間
while(p2+1 < len && (pp[p1]^pp[p2+1]) == 0)p2++;
cnt[p2-p1+2]+=1;
p1 = p2+1;
}
}
signed main(){
Orz;
cin>>n;
p.assign(n,{0,0});
cnt.resize(n+1,0);
rep(i,0,n-1)cin>>p[i].x>>p[i].y;
rep(i,0,n-1)solve(p[i]);
int ans = (n*(n-1)*(n-2))/6;
rep(i,3,n)ans-=(cnt[i]*(i-1)*(i-2))/6;
cout<<ans<<endl;
}
給定二維平面上\(n\)個點,問一條直線最多能穿越幾個點?
$$1\leq n\leq 300$$
$$-10^4\leq x_i,y_i\leq 10^4$$
Convex Hull