【筆記篇】最良心的計算幾何學習筆記(三)

廣告:
1. 先是放一下本文的::github傳送門:: (不知道爲什麼要放)
2. 今天發現了一個AMA(Ask me anything)的東西, 覺得非常好玩, 就fork了一個放到自己的github裏面,
估計沒有人會來問, 所以就放到這裏拉攏人氣(雖然這裏也拉攏不到) 歡迎大家來玩哦~
地址請戳這個就是傳送門啦~

今天繼續計算幾何(明明已經頹廢了半下午了

計算多邊形面積

我們先從最簡單的多邊形——三角形開始看.
這是一個三角形

如何計算ABC 的面積? 這個問題數學課上老師應該說過..

  • S=12ah
    這個是最最最常見的公式, 但是這裏我們並不知道高, 要算起來就比較麻煩.

  • S=12bcsinA (其他兩角同理)
    這個看上去比較靠譜. 我們算一下AB×AC絕對值就好了…

  • S=p(pa)(pb)(pc),p=a+b+c2
    海倫-秦九韶公式啊OvO 這個是可以算的… 但是如果用在多邊形就不是很好用了.

簡單的三角形我們看完了, 我們來看看多邊形..
有些多邊形我們都已經熟知面積公式(比如長方形啊 平行四邊形啊 梯形啊什麼的)
就不再提了.
來看看凸多邊形…
還記得上次可愛的凸多邊形麼_ (:з」∠)_?
凸多邊形面積

我們要計算它的面積的時候, 只需要像圖中一樣劃分成若干個三角形, 然後用公式

S=i=17bcsinA=i=17|12Ei×Ei+1|

計算總面積即可.

但是對於凹多邊形呢? 我們還是先劃一下三角形…
凹多邊形面積

我們會發現如果再按照上面的方法計算的話黃色和紫色(似乎故意標淡了點)的面積會重複計算, 顯然是大於多邊形面積的. 但是數學老師教的面積公式畢竟還是和我們的叉積不一樣的, 公式算的是絕對值, 而叉積是有正負的.
如果EiEi+1 的順時針方向, 面積算出來的是正值; 否則算出來的是負值.
發現剛纔重複的黃色和紫色部分如果用叉乘算一下剛好是一正一負, 多餘的面積都不見了..
再再求一波總和就做完了, 輕鬆加愉快…
而且有了這種正負的定義之後, 我們又有了一種新操作:
這可是最新操作!
以某個點爲出發點向多邊形做向量, 一路做叉積繞個圈求出來的和也等於面積~
爲了方便起見, 完全可以讓”某個點”取原點O , 這樣從數值上來說我們只需要把點的座標做叉積即可了~

且慢! 不是還有一種自我重疊的多邊形嗎? 這個方法也適用嗎?
這個我就不配圖了(其實是嫌麻煩←_←) 完全可以自己畫一下..
發現是完全適用的, 而且自我重疊的部分的面積會計算正確的次數哦~

然後就是最後的總面積有可能是個負值, 可以視情況取個絕對值什麼的^_^

貼代碼(仍然並沒有找到板子題~)(似乎是因爲代碼太簡單了?

//求任意多邊形面積
double polyArea(point *pts,int pcnt,double s=0){
    pts[pcnt]=pts[0];
    for(int i=0;i<pcnt;++i)
        s=pts[i]*pts[i+1]+s;
    return 0.5*s;
}

這樣就搞定了OvO

計算多邊形重心

這個也分很多情況啊OvO
而且這個涉及到了高端的數學及物理知識(頭疼ing…

質量集中在頂點上

那就是每個頂點的質量關於座標的平均咯~

X=n1i=0ximin1i=0miY=n1i=0yimin1i=0mi
質量均勻分佈

還是從簡單開始, 三角形的重心.
懶得再推了, 數學老師說座標應該是(x1+x2+x33,y1+y2+y33) ..
所以我們就同樣可以把多邊形三角剖分, 每個三角形的質量都等效到中心去.
然後就變成了質量集中在頂點上的情況, 質量就取三角形的面積(注意是有向面積)即可.
要注意的比如總面積是0的時候, 因爲要做分母, 所以要特殊處理.
板子題hdu1115適合寫一下.
不過又被-0.00卡翻.. 做了一波優化把13 提出來竟然就A掉了, 不是很懂OvO
代碼:

#include <cmath> 
#include <cstdio>
const double eps=1e-9;
int dcmp(const double &a){
    if(fabs(a)<eps) return 0;
    return a<0?-1:1;
}
struct point{
    double x,y;
    point(double X=0,double Y=0):x(X),y(Y){}
}poly[1000010],s;
double operator *(const point &a,const point &b){
    return a.x*b.y-a.y*b.x;
}
point polyCenter(point *pts,int pcnt,double sx=0,double sy=0,double area=0){
    pts[pcnt]=pts[0]; double ar;
    for(int i=0;i<pcnt;++i){
        ar=pts[i]*pts[i+1];
        sx+=(pts[i].x+pts[i+1].x)*ar; //這裏如果寫sx+=(pts[i].x+pts[i+1].x)/3*ar;
        sy+=(pts[i].y+pts[i+1].y)*ar; //這個地方寫sy+=(pts[i].y+pts[i+1].y)/3*ar;
        area+=ar;
    } area*=3; //而這個地方不寫的話就會被卡精度:-(
    return point(sx/area,sy/area);
}
int main(){
    int T; scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        for(int i=0;i<n;++i)
            scanf("%lf%lf",&poly[i].x,&poly[i].y);
        s=polyCenter(poly,n);
        printf("%.2lf %.2lf\n",s.x,s.y);
    }
}
質量不均勻分佈

這個據說要用到積分?
反正我是不會的←_←
等見到再考慮學不學吧..
估計(希望)我是見不到了(flag

然後還有一些點或許因爲太麻煩, 或許因爲不常見還沒有學到..
比如什麼求多邊形之內最大的圓之類的.
據說特別麻煩, 等到有空或者用得到的時候再研究吧.
下次就該學學”更計算幾何”的一些知識了
比如凸包.

再隨便多說幾句:
遇到多邊形的問題要先考慮(讀題)看分不分凹凸, 是不是簡單.
一般讓多邊形第n個點等於第0個點做起來會很舒服.
計算幾何都是毒瘤題見到還是直接棄療吧←_←

但是這篇文章的長度似乎不太夠…
我們再加一丟丟內容吧…

平面最近點對

暴力枚舉每一對顯然就是O(n2) 的, 那是很顯然過不了的.
似乎有一些玄學的做法比如隨機轉個角度防卡然後分塊, 但是這種做法看着就不科學…
我們要思考科學的方法, 比如考慮分治解決問題.
平面最近點對

先將所有點按橫座標排個序.
最近點對的這兩個點的分佈只可能有三種情況:
都在左邊、都在右邊、左右各一.
對於前兩種情況遞歸下去即可.
我們主要來處理左右各一的情況.
我們假設左右兩邊遞歸後求出的值的較小者爲d .(圖中的r)
那很顯然我們只需要考慮[mid-d,mid]和[mid+d,mid]中的點.
如果還是暴力 比較壞的情況複雜度跟暴力並沒有什麼區別, 還是O(n2) 的.
但是因爲要求的是最近點對, 所以我們可以限制一波.

平面最近點對

對於左側的P點來說, 假如說d 是左右兩邊求的最小值, 顯然我們要找的點和p 之間的橫座標是要小於d
而這個矩形中最多放多少個互相距離不超過d 的點呢? 答案是6個.
爲什麼呢? 我們將寬平均分成2份, 高平均分成3份,(紅色) 這樣就形成了一個2*3的格子.
每個格子的寬就是12d , 高就是23d , 由勾股定理, 對角線(藍色)長爲(12d)2+(23d)2=56d<d
也就是說不可能存在一個格子中能存在兩個距離大於d 的點.
那麼根據抽屜原理, 最多就只有6個點了.
所以我們只需要找這些點進行檢索即可, 這樣就保證複雜度不會太高了.

還有一點小細節就是我們按y 找的時候可以採用歸併的方式, 每次只排子序列, 這樣可以把複雜度控制在O(nlogn) 級別, 這樣這個問題就得到了完美的解決.
代碼:

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
const double eps=1e-9;
int dcmp(const double &a){
    if(fabs(a)<eps) return 0;
    return a<0?-1:1; 
}
struct point{
    double x,y;
    point(double X=0,double Y=0){}
}p[200020];
int t[200020];
inline bool cmpx(const point &a,const point &b){
    if(a.x==b.x) return a.y<b.y;
    return a.x<b.x;
}
inline bool cmpy(const int &a,const int &b){
    return p[a].y<p[b].y;
}
inline double dist(const point &a,const point &b){
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double solve(int l,int r){
    if(r==l) return 1e9;
    if(r-l==1) return dist(p[l],p[r]);
    int mid=(l+r)>>1;
    double dl=solve(l,mid);
    double dr=solve(mid+1,r);
    if(dr<dl) dl=dr;

    int tot=0; double dis=0;
    for(int i=l;i<=r;++i)
        if(dcmp(fabs(p[i].x-p[mid].x)-dl)<0)
            t[tot++]=i; //合法的點才加入數組
    sort(t,t+tot,cmpy);
    for(int i=0;i<tot;++i)
        for(int j=i+1;j<tot&&p[t[j]].y-p[t[i]].y<dl;++j){
            if((dis=dist(p[t[i]],p[t[j]]))<dl) dl=dis;
        } //左右兩邊都在搜所以只需要考慮下半個矩形
    return dl;
}
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar())a=a*10+c-48;return a;
}
int main(){
    int n=gn();
    for(int i=1;i<=n;++i) p[i].x=gn(),p[i].y=gn();
    sort(p+1,p+n+1,cmpx);
    printf("%.4lf",solve(1,n));
} 

那麼就這樣咯~
這一篇我竟然拖了兩天..

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章