計算幾何入門

精度問題

計算機精度實在不敢恭維,等於號幾乎沒有用,因此要引入一個極小量eps(1e-8什麼的)php

\(a==b \qquad \to \qquad fabs(a-b)<eps\)ios

\(a>b \qquad \to \qquad a-b>eps\)git

\(a<b \qquad \to \qquad a-b<-eps\)算法

基礎概念

1.點

不知道點是什麼的能夠走了spa

通常用座標表示3d

struct Point
{
    double x,y;
};

2.向量

沒學過向量的能夠翻翻高中數學或自行百度,不想說了code

咱們也把向量用座標(x,y)表示blog

因此通常和點共用一個結構體排序

若是有強迫症能夠隊列

typedef Point Vector;

模長:就是向量的長度,記爲\(|A|\)

double length(const Vector& v)
{
    return sqrt(v.x*v.x+v.y*v.y);
}

<A,B>表示A和B的夾角

計算機算解析式極其繁瑣,因此計算幾何中幾乎全部操做都是用向量實現的

3.直線與線段

通常用兩個點或一個點和一個向量表示

其實差很少

向量運算

強烈建議你們學一下重載運算符,不會也不要緊

點-點=向量

就是\(B-A=\overrightarrow {AB}\)

Vector operator -(const Point& a,const Point& b)
{
    return (Vector){a.x-b.x,a.y-b.y};
}

點和向量加減

Point operator +(const Point& p,const Vector& v)
{
    return (Point){p.x+v.x,p.y+v.y};
}

減法同理

向量乘常數

把向量伸長或縮短,或者反向

不要寫成int了

Vector operator *(const Vector& a,const double& k)
{
    return (Vector){a.x*k,a.y*k};
}

向量加減

和向量加減點同理

點積(很重要)

定義:\(A·B=|A||B|cos<A,B>\)

是個標量(沒有方向)

座標:\(A·B=x_{A} y_{A}+x_{B} y_{B}\)

double operator ^(const Vector& a,const Vector& b)//用^代替,由於叉積用的多
{
    return a.x*b.x+a.y*b.y;
}

幾何意義:A在B所在方向的投影的模長和B的模長的乘積

能夠判兩個向量是否垂直

叉積(更重要)

定義:\(A \times B=|A||B|sin<A,B>\)

原本是向量,但它垂直於平面,你當標量好了

座標:\(A·B=x_{A} y_{B}-x_{B} y_{A}\)

double operator *(const Vector& a,const Vector& b)
{
    return a.x*b.y-a.y*b.x;
}

幾何意義:A轉到B所夾平行四邊形的有向面積

有向指

這個用途多的多,能夠判斷A和B哪一個在前,A、B是否共線等。

基礎問題

點P是否在線段AB上

\(|PA|+|PB|=|AB|\)

點P是否在直線AB上

\(\overrightarrow {PA} \times \overrightarrow {PB} =0\)

ABC拐向

\(\overrightarrow {AB} \times \overrightarrow {AC} >0\):逆時針拐彎

凸多邊形面積

\(S=\frac{1}{2} \sum _{i=1}^{n} P_{i} \times P_{i+1}\)

求直線交點

列方程太麻煩,考慮將向量發揚光大

畫出向量

考慮算高

由叉積的幾何意義,兩條粉色虛線之比爲(RE藍×WA紅:AC綠×WA紅)

由類似,等於(無色英雄:P2到交點)

即把原諒綠延長再加上P2

算法

凸包

定義:最小的凸多邊形覆蓋全部點

Granham掃描法

①選出x座標最小的點,若是相同選y座標最小(必定在凸包上),將這個點壓入棧

②全部點按極角(和最開始的點的連線與x軸的夾角)排序

③跑一遍,一直彈棧直到(s[top-1],s[top],當前點)向左拐

複雜度O(nlogn)

容易被卡精度

不知道叫什麼的改進

按x第一關鍵字,y第二關鍵字排,跑完後再倒着跑一遍

精度相對較高

模板題

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define eps 1e-8
using namespace std;
struct Point
{
    double x,y;
}p[10005];
typedef Point Vector;
bool operator < (const Point& a,const Point& b)
{
    return a.x==b.x? a.y<b.y:a.x<b.x;
}
double operator *(const Vector& a,const Vector& b)
{
    return a.x*b.y-a.y*b.x;
}
Vector operator -(const Point& a,const Point& b)
{
    return (Vector){a.x-b.x,a.y-b.y};
}
double mod(const Vector& v)
{
    return sqrt(v.x*v.x+v.y*v.y);
}
int n;
int st[10005];
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%lf%lf",&p[i].x,&p[i].y);
    sort(p+1,p+n+1);
    int m=1;
    double ans=0.0;
    st[1]=1;
    for (int i=2;i<=n;i++)
    {
        while (m>1&&(p[st[m]]-p[st[m-1]])*(p[i]-p[st[m-1]])<=0) m--;
        st[++m]=i;
    }       
    int k=m;
    for (int i=n-1;i>=1;i--)
    {
        while (m>k&&(p[st[m]]-p[st[m-1]])*(p[i]-p[st[m-1]])<=0) m--;
        st[++m]=i;
    }
    for (int i=1;i<m;i++)
    {
        ans+=mod(p[st[i+1]]-p[st[i]]);      
    }
    printf("%.2lf",ans);
    return 0;
}

旋轉卡殼

關於此算法讀音:一共24種

英文叫Rotating calipers,直譯「旋轉的遊標卡尺」(蛤?)

也就是說卡殼指的是遊標卡尺。因此卡殼是個名詞,那應該讀qiǎ ké

這個算法主要用於算一堆點的最長距離

其實也沒名字說的那麼奧妙

①求出凸包,特判只有2個點的狀況

②假想有一組平行直線把凸包夾着

經過算面積能夠找出離左邊直線也許最遠的點,當它比下一個點長時,就中止,用左邊兩個點到右邊的距離更新答案。

③把直線轉一下,和下一條邊重合,在原來的基礎上把點日後挪,一樣找個貌似是最遠的點

(mspaint不能自定義旋轉,兩個可能長得不同,望理解)

我也不知道爲何反正就是對的

直線最多轉一圈,因此是O(n)的

模板

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <stack>
#include <cmath>
#define MAXN 50005
#define nxt(x) ((x)%top+1)
using namespace std;
inline int read()
{
    int ans=0,f=1;
    char c=getchar();
    while (!isdigit(c))
    {
        if (c=='-') f=-1;
        c=getchar();
    }
    while (isdigit(c))
        ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
    return f*ans;
}
struct Point
{
    int x,y;
}p[MAXN];
int s[MAXN];
int top=1;
typedef Point Vector;
bool operator <(const Point& a,const Point& b)
{
    return a.x==b.x? a.y<b.y:a.x<b.x;
}
int operator *(const Vector& a,const Vector& b)
{
    return a.x*b.y-a.y*b.x;
}
Vector operator -(const Point& a,const Point& b)
{
    return (Vector){a.x-b.x,a.y-b.y};
}
int dis(const Vector& v)
{
    return v.x*v.x+v.y*v.y;
}
int area(const Point& x,const Point& y,const Point& z)
{
    return (y-x)*(z-x);
}
int n;
void gethull()
{
    sort(p+1,p+n+1);
    s[1]=1;
    for (int i=2;i<=n;i++)
    {
        while (top>1&&(p[s[top]]-p[s[top-1]])*(p[i]-p[s[top-1]])<=0)
            top--;
        s[++top]=i;
    }
    int k=top;
    for (int i=n-1;i>=1;i--)
    {
        while (top>k&&(p[s[top]]-p[s[top-1]])*(p[i]-p[s[top-1]])<=0)
            top--;
        s[++top]=i;
    }
}
int getmax()
{
    int ans=0;
    if (top==2)
        return dis(p[s[2]]-p[s[1]]);
    s[top+1]=s[1];
    for (int i=1,j=3;i<=top;i++)
    {
        while (nxt(j)!=i&&area(p[s[i]],p[s[i+1]],p[s[j]])<=area(p[s[i]],p[s[i+1]],p[s[j+1]]))
            j=nxt(j);
        ans=max(ans,max(dis(p[s[j]]-p[s[i]]),dis(p[s[j]]-p[s[i+1]])));
    }
    return ans;
}
int main()
{
    n=read();
    for (int i=1;i<=n;i++)
        p[i].x=read(),p[i].y=read();
    gethull();
    printf("%d",getmax());
    return 0;
}

半平面交

問題:給定一些半平面,求它們的交

細節問題:

1.半平面指在給出的直線一邊的平面,具體哪一邊看題。由凸多邊形定義,它必定是凸多邊形

2.交能夠是多邊形,還能夠是直線、射線、線段、點、空集,也能夠不封閉。具體看題。

爲了方便理解,先放一道題

算法流程:

①全部直線按和x軸夾角排序;若是有相同的,只保留最優的(上面的題是更靠「內」的,寫寫就知道了)

②維護一個雙端隊列(由於座標軸上轉了一圈會回來)

跑一遍全部直線

while(當前直線在隊首兩直線交點外)//「外」是指加入這條直線後前兩個半平面的交會變化
彈隊首;
while(當前直線在隊尾兩直線交點外)
彈隊尾;

③清除多餘直線

while(隊尾直線在隊首兩直線交點外)
彈隊首;
while(隊首直線在隊尾兩直線交點外)
彈隊尾;

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 505
#define MAXM 55
const double eps=1e-8;
using namespace std;
struct Point
{
    double x,y;
}p[MAXN];
typedef Point Vector;
double operator *(const Vector& a,const Vector& b)
{
    return a.x*b.y-a.y*b.x;
}
Vector operator *(const Vector& a,const double& k)
{
    return (Vector){a.x*k,a.y*k};
}
Point operator +(const Point& p,const Vector& v)
{
    return (Point){p.x+v.x,p.y+v.y};
}
struct Line
{
    Point a,b;
    double d;
}l[MAXN];
int sta[MAXN];
int top;
Vector operator -(const Point& a,const Point& b)
{
    return (Vector){a.x-b.x,a.y-b.y};
}
Point inter(const Line& x,const Line& y)
{
    Point p1=x.a,p2=y.a;
    Vector v1=x.b-p1,v2=y.b-p2,u=p2-p1;
    Point p=p2+v2*((u*v1)/(v1*v2));
    return p;
}
int sign(const double& a)
{
    if (fabs(a)>eps)
        return a>0? 1:-1;
    return 0;
}
bool operator <(const Line& a,const Line& b)
{
    if (sign(a.d-b.d)==0)
        return sign((a.b-a.a)*(b.b-a.a))>0;
    return sign(a.d-b.d)<0;
}
int n,m;
int q[MAXN],head,tail;
bool check(const Line& x,const Line& y,const Line& z)
{
    Point p=inter(x,y);
    return sign((z.b-z.a)*(p-z.a))<0;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
    {
        int mi;
        scanf("%d",&mi);
        for (int j=1;j<=mi;j++)
            scanf("%lf%lf",&p[j].x,&p[j].y);
        p[mi+1]=p[1];
        for (int j=1;j<=mi;j++)
        {
            ++m;
            l[m].a=p[j];
            l[m].b=p[j+1];
        }
    }
    n=m;
    for (int i=1;i<=n;i++)
        l[i].d=atan2(l[i].b.y-l[i].a.y,l[i].b.x-l[i].a.x);
    sort(l+1,l+n+1);
    int cnt=0;
    for (int i=1;i<=n;i++)
    {
        if (sign(l[i].d-l[i-1].d)!=0)
            ++cnt;
        l[cnt]=l[i];
    }
    head=1,tail=0;
    q[++tail]=1,q[++tail]=2;
    for (int i=3;i<=cnt;i++)
    {
        while (head<tail&&check(l[q[tail-1]],l[q[tail]],l[i]))
            --tail;
        while (head<tail&&check(l[q[head+1]],l[q[head]],l[i]))
            ++head;
        q[++tail]=i;
    }
    while (head<tail&&check(l[q[tail-1]],l[q[tail]],l[q[head]]))
        --tail;
    while (head<tail&&check(l[q[head+1]],l[q[head]],l[q[tail]]))
        ++head;
    q[tail+1]=q[head];
    n=0;
    for (int i=head;i<=tail;i++)
        p[++n]=inter(l[q[i]],l[q[i+1]]);
    p[n+1]=p[1];
    double ans=0;
    if (n>2)
        for (int i=1;i<=n;i++)
            ans+=(p[i]*p[i+1]);
    ans=fabs(ans)/2.0;
    printf("%.3f",ans);
    return 0;
}
相關文章
相關標籤/搜索