計算幾何-凸包
計算幾何-凸包
二維凸包
凸多邊形
凸多邊形是指所有內角在\(\left[ 0,\pi \right]\)範圍內的簡單多邊形。
凸包
對於在平面上的一個點集,凸包是能包含所有點的最小凸多邊形。
其定義為:對於給定集合\(D\),所有包含\(D\)的凸集的交集\(S\)被稱為\(D\)的凸包。
如:
凸包求法
對於平面上的一個點集,其凸包可以用分治,\(Graham-Scan\),和 \(Andrew\)。
分治
對於分治演算法解決凸包問題,遞迴求解,找到子問題的凸包,講左右兩個子集的凸包進進行合併即可,時間複雜度\((n \log n)\)。
\(Andrew\)
對於\(Andrew\)演算法,主要流程:
-
首先將所有點以橫座標為第一關鍵字,縱座標為第二關鍵字進行排序;
-
顯然排序後最小的元素和最大的元素一定在凸包上,然後用單調棧維護上下凸殼;
-
因為上下凸殼所旋轉的方向不同,我們首先升序列舉下凸殼,然後降序列舉上凸殼。
時間複雜度\((n \log n)\)。
\(Graham-Scan\)
主要介紹這種演算法,更好寫一些,首先我們先找出在最右下角的點,此時這個點一定在凸包上,然後我們從這個點開始逆時針旋轉,同時用單調棧維護凸包上的點,每加入一個新點是判斷改點是否會出現,該邊在上一條邊的“右邊”,如果出現則刪除上一個點。
Code:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10; int n,top;double ans; struct geometric{ double x,y,dss; friend geometric operator + (const geometric a,const geometric b){return (geometric){a.x+b.x,a.y+b.y};} friend geometric operator - (const geometric a,const geometric b){return (geometric){a.x-b.x,a.y-b.y};} double dis(geometric a,geometric b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));} double dot(geometric a1,geometric a2,geometric b1,geometric b2){return (a2.x-a1.x)*(b2.x-b1.x)+(a2.y-a1.y)*(b2.y-b1.y);} double cross(geometric a1,geometric a2,geometric b1,geometric b2){return (a2.x-a1.x)*(b2.y-b1.y)-(a2.y-a1.y)*(b2.x-b1.x);} }; geometric origin,data[maxn],st[maxn]; bool vis[maxn]; bool cmp(geometric a,geometric b) { geometric opt; double tamp=opt.cross(data[1],a,data[1],b); if(tamp>0)return true; if(tamp==0&&opt.dis(data[1],a)<=opt.dis(data[1],b))return true; return false; } geometric opt; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%lf%lf",&data[i].x,&data[i].y); if(i!=1&&data[i].y<data[1].y) { double tmp; swap(data[1].y,data[i].y); swap(data[1].x,data[i].x); } if(i!=1&&data[i].y==data[1].y&&data[i].x>data[1].x) swap(data[1].x,data[i].x); } sort(data+2,data+n+1,cmp); st[++top]=data[1]; for(int i=2;i<=n;i++) { while(top>1&&opt.cross(st[top-1],st[top],st[top],data[i])<=0)top--; st[++top]=data[i]; } st[++top]=data[1]; for(int i=1;i<top;i++)ans+=opt.dis(st[i],st[i+1]); printf("%.2lf",ans); return 0; }
動態凸包
首先我們考慮這樣一個問題:
兩種操作:
- 向點集中新增一個點\((x,y)\);
- 詢問點是否在凸包中。
首先我們對於一個動態的凸包,很明顯在每次加入新點時不能再進行一次\(Graham-Scan\),否則時間複雜度不優。
那麼我們就可以用一下方法:
-
首先建一棵平衡樹,按極角排序;
-
詢問是找到該點的前驅後繼,用叉積判斷即可,否則執行插入操作;
-
插入時,先將點插入平衡樹內然後找該點的前驅後繼,同時不斷去旋轉將在凸包內的點刪去。
對於平衡樹我們可以直接用\(STL\)裡的\(set\) 去實現,需要用到迭代器。
Code:
#include<bits/stdc++.h> #define it set<geometric>::iterator #define eps 1e-8 using namespace std; const int maxn=1e5+10; int Sure(double x){return fabs(x)<eps?0:(x<0?-1:1);} struct geometric{ double x,y; geometric(double X=0,double Y=0):x(X),y(Y) {} friend geometric operator + (const geometric a,const geometric b){return geometric(a.x+b.x,a.y+b.y);} friend geometric operator - (const geometric a,const geometric b){return geometric(a.x-b.x,a.y-b.y);} friend geometric operator * (const geometric a,double p){return geometric(a.x*p,a.y*p);} friend geometric operator / (const geometric a,double p){return geometric(a.x/p,a.y/p);} double dis(geometric a,geometric b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));} double dot(geometric a1,geometric a2,geometric b1,geometric b2){return (a2.x-a1.x)*(b2.x-b1.x)+(a2.y-a1.y)*(b2.y-b1.y);} double cross(geometric a1,geometric a2,geometric b1,geometric b2){return (a2.x-a1.x)*(b2.y-b1.y)-(a2.y-a1.y)*(b2.x-b1.x);} double corner(geometric a1,geometric a2,geometric b1,geometric b2){return dot(a1,a1,b1,b2)/(dis(a1,a2)*dis(b1,b2));} double area(geometric a1,geometric a2,geometric b1,geometric b2){return fabs(cross(a1,a2,b1,b2));} double angle(geometric a){return atan2(a.y,a.x);} geometric rotate_clockwise(geometric a,double theta){return geometric(a.x*cos(theta)-a.y*sin(theta),a.x*sin(theta)+a.y*cos(theta));} geometric rotate_counterclockwise(geometric a,double theta){return geometric(a.x*cos(theta)+a.y*sin(theta),-a.x*sin(theta)+a.y*cos(theta));} }opt,d[maxn],origin; bool operator < (geometric a,geometric b){ a=a-origin;b=b-origin; double ang1=atan2(a.y,a.x),ang2=atan2(b.y,b.x); double l1=sqrt(a.x*a.x+a.y*a.y),l2=sqrt(b.x*b.x+b.y*b.y); if(Sure(ang1-ang2)!=0)return Sure(ang1-ang2)<0; else return Sure(l1-l2)<0; } int q,cnt; set<geometric> S; it Pre(it pos){if(pos==S.begin())pos=S.end();return --pos;} it Nxt(it pos){++pos; return pos==S.end() ? S.begin():pos;} bool Query(geometric key) { it pos=S.lower_bound(key); if(pos==S.end())pos=S.begin(); return Sure(opt.cross(*(Pre(pos)),key,*(Pre(pos)),*(pos)))<=0; } void Insert(geometric key) { if(Query(key))return; S.insert(key); it pos=Nxt(S.find(key)); while(S.size()>3&&Sure(opt.cross(*(Nxt(pos)),*(pos),*(Nxt(pos)),key))>=0) { S.erase(pos);pos=Nxt(S.find(key)); } pos=Pre(S.find(key)); while(S.size()>3&&Sure(opt.cross(*(Pre(pos)),*(pos),*(Pre(pos)),key))<=0) { S.erase(pos);pos=Pre(S.find(key)); } } int main() { scanf("%d",&q); for(int i=1;i<=3;i++) { int opt;double x,y;scanf("%d%lf%lf",&opt,&x,&y); d[++cnt]=geometric(x,y);origin.x+=x;origin.y+=y; } origin=origin/3.0; for(int i=1;i<=3;i++)S.insert(d[i]); for(int i=4;i<=q;i++) { int opt;double x,y; scanf("%d%lf%lf",&opt,&x,&y); d[++cnt]=geometric(x,y); if(opt==1)Insert(d[cnt]); else { if(Query(d[cnt]))printf("YES\n"); else printf("NO\n"); } } return 0; }
注意使用迭代器時不要越界,要和用指標一樣小心!
三維凸包
咕咕咕