凸包復習
幾何專題刷了有大半年了,突然發現以前學的竟然忘的差不多了,下午又花了點時間復習一下,感覺挺簡單的(全是靠模板。。
資料上沒有適合自己的模板,於是復習一下自己整理一下模板。
先來接觸點預備函數:
一、 點的定義:
int n,tot;//n為二維平面上點的個數,tot為凸包上點的個數 struct node { int x,y; }a[N],p[N];//p[]用來儲存凸包
二、距離公式:
double dis(node a,node b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); }三、 叉積:返回結果為正說明p2在向量p0p1的左邊(三點構成逆時針方向);為負則相反;為0則三點共線(叉積的性質很重要)
double multi(node p0,node p1,node p2) { return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y); }四、 極角排序: 極角排序是根據坐標系內每一個點與x軸所成的角,逆時針比較,。按照角度從小到大的方式排序。
int cmp(node p1,node p2)//極角排序; { int x=multi(p1,p2,a[0]); if(x>0||(x==0&&dis(p1,a[0])<dis(p2,a[0]))) return 1; return 0; }graham 算法:O(nlogn)
void Graham() { int k=0; for(int i=0;i<n;i++) if(a[i].y<a[k].y||(a[i].y==a[k].y&&a[i].x<a[k].x)) k=i; swap(a[0],a[k]); sort(a+1,a+n,cmp); tot=2,p[0]=a[0],p[1]=a[1]; for(int i=2;i<n;i++) { while(tot>1&&multi(p[tot-1],p[tot-2],a[i])>=0) tot--; p[tot++]=a[i]; } }
以上連接起來就是求凸包的模板。
光有代碼不行,還得懂原理
附一篇較好的博客:傳送門
看懂了那篇博客基本上凸包就已經會了。
先用一個經典問題來引入吧: 在一片有限區域的草坪上有n個木樁(n>=3),現在要求用一根繩子將這些木樁圍起來,求所需繩子的最短周長。
以上問題中繩子所圍成的圖形就是一個凸包。要求周長,那么必須先要求出繩子接觸了哪些木樁。
求凸包有若干種方法,這里只介紹Graham算法。
大致思路:先確定凸包上一個點,再用這個點作為參照將剩余的點進行極角排序,根據性質可以得到凸包上的第二個點,再用已知的凸包上的點利用叉積的性質進行確定下一個點,直到圍成一個凸包。
① 先確定凸包上的一個點,我們知道橫縱坐標最大或最小的點肯定在凸包上,我們就選取縱坐標最小的點作為第一個點,如果有多個縱坐標最小的點怎么辦呢,我們選取橫坐標最小的點。這是為極角排序做准備。
② 極角排序:上面提到了極角排序的定義,我們可以以選取的第一個點p0作為原點(參照),其他點與p0點的連線與x軸的夾角進行排序,如果夾角相同怎么辦呢,按與p0的距離排序,這一步考驗對叉積的性質利用。
③ 除了p0,排序后的第一個點p1和最后一個點一定是凸包上的點,想想為什么。這樣我們就得到了p0,p1,我們把它們放入棧里,現在用p0,p1來確定p2(棧頂的兩個點來確定下一個點),還是用叉積的性質,棧頂兩個點連成線(向量),看當前點是在直線的左邊還是右邊,如果在右邊,說明棧頂的那個點不是凸包上的點,退棧即可,然后重復判斷棧頂兩個元素與當前元素的關系;反之,則說明當前點是凸包上的點。
④ 將當前點入棧,如果當前點不是凸包上的點,后面的點自然會將這個點gank。對下一個點進行相同的操作。最后棧中的點就是凸包上的點啦。
詳細請參考上面代碼。
推薦幾道例題吧:
NYOJ-78 圈水池 入門凸包輸出點
POJ-2007 Scrambled Polygon 求凸包並且按原點為第一點的逆時針方向輸出
HDU-1392 Surround the Trees 入門求凸包周長
POJ-3348 Cows 入門求面積
POJ-2187 Beauty Contest 旋轉卡殼求平面最遠點對
POJ-1228 Grandpa's Estate 穩定凸包
POJ-1696 Space Ant 凸包應用(德黑蘭賽區好題 )
POJ-1113 Wall 凸包簡單應用(推薦)