貌似是第一次做計算幾何的題, 以前都是瞭解裡面些概念但沒做過題....主要是沒精力來完善到這一塊....而且比賽的時候"疲於奔命"似的做其他被人刷爆的題目.....
邊講題邊講我對計算幾何初步的理解.....
POJ 2365 Rope求一個凸包的周長, 題目已經是按順序給出凸包的各個點, 所以直接求鄰點間距離就為切線距離.而弧長距離, 由於是內角和是2PI, 所以最後加上一個圓周長即可.涉及概念:兩點歐式距離
double dist(POINT p1,POINT p2) // 返回兩點之間歐氏距離{ return( sqrt( (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y) ) );}POJ 2007 Scrambled Polygon給你一個凸包, 其中必有一個原點, 要你將凸包上的點按關於原點(0, 0) 逆時針排序.思路 :首先可以得出幾個結論, 1/ 所有點最多分布在三個象限. 2/ 原點到任何兩個點的向量的夾角必然小於pi.這題其實是按極角排序, 而極角排序可以依據 叉積大小 或 atan2大小.涉及概念:一 極角兩個向量a,b,以a為極軸建極座標系,b與a的夾角就是極角,極角可以取任意弧度.(據說數學上一般取[0,2pi) ).二 叉積兩向量a,b的叉積的大小是以a,b為邊的平行四邊形的面積,符號為逆時針旋轉為正,順時針為負.大量用於判斷幾何位置關係,也可以用來求面積.公式為 x1*y2-x2*y1.三 atan2函數這個在<cmath>標頭檔裡, 它接受一個向量,返回一個弧度表示這個向量在座標繫上的位置,從-x軸沿原點轉一圈回到-x,弧度範圍是(-pi,pi].而這個題是極角排序, 但是我們只能兩兩比較(偏序關係), 來排序, 我們知道任意兩個向量夾角是小於pi的, 那我們正好用叉積可以比, 叉積為正表示逆時針, 為負表示順時針.
int cross(point p0,point p1,point p2){ return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x);} bool cmp(point p1,point p2){ int tmp=cross(a[0],p1,p2); if(tmp>0) return true; else return false;}
那用atan2函數, 怎麼比呢? 題目沒有說所佔的三個象限不跨越-x軸, 那 atan2 的差就有可能大於 pi, 或小於 -pi, 也就是說它取的不一定是兩個向量小於pi的那個角. 所以我們要經過一些處理 .
bool cmp(const point &a, const point &b){double angle1 = atan2(a.y, a.x);double angle2 = atan2(b.y, b.x);double angle = angle1-angle2;if(angle>pi) angle-=2*pi;if(angle<-pi) angle = 2*pi+angle;return angle<0;}
POJ 1269 Intersecting Lines題目就叫相交直線, 都直白了吧. 這題就是判下兩線段位置關係.注意: 線段是segment, 直線是line...涉及概念:一 直線位置關係平行: 不相交, 或者覆蓋. 可以判斷叉積為0就是平行, 覆蓋的話可以判斷共線相交: 兩直線交與一點.
//計算cross product (P1-P0)x(P2-P0)double xmult(point p1,point p2,point p0){return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);}//判三點共線int dots_inline(point p1,point p2,point p3){return zero(xmult(p1,p2,p3));}//判兩直線平行int parallel(line u,line v){return zero((u.a.x-u.b.x)*(v.a.y-v.b.y)-(v.a.x-v.b.x)*(u.a.y-u.b.y));}POJ 1654 Area 這題就是給一個從原點開始然後又回到原點的路徑序列, 求走出的多邊形的面積.我們只要維護前一個點, 然後每次把當前點到前一個點到原點的三角形面積加到結果中就行了.這裡求面積可用用叉積, 求出來的是平行四邊形的面積, 最後再除以二即可.
int64 getarea(Point p1,Point p2){ return (int64)p1.x*p2.y-(int64)p2.x*p1.y;//叉積為三角形面積兩倍}
POJ 2318 TOYS 這題只需解決點跟線段的位置關係(點線上段的哪一邊)就行了. 可用利用叉積.
//判兩點線上段同側,點線上段上返回0int same_side(point p1,point p2,line l){return xmult(l.a,p1,l.b)*xmult(l.a,p2,l.b)>eps;}//判兩點線上段異側,點線上段上返回0int opposite_side(point p1,point p2,line l){return xmult(l.a,p1,l.b)*xmult(l.a,p2,l.b)<-eps;//叉積異號}
那麼, 這題要判定的是 一點 在兩線段中間(含點線上段上)的情況.只需做一點改變,
//判斷兩直線在點兩邊int is(line l1, line l2, point p){int ret = xmult(l1.a, p, l1.b)*xmult(l2.a, p, l2.b)>eps;return !ret;}
POJ 3304 Segments
這題比前面的題都難想點.
顯然, 問題等價於是否存在任意一條直線使它過題目中所有線段.那麼, 極限思想, 假設存在這條直線, 我們將它旋轉平移, 使它恰好過兩個線段的某個端點, 這一定是可以做到的.那麼, 我們只需兩兩枚舉所有線段端點即可, 看過它們的直線是否滿足題目.核心代碼:
bool solve(){REP(n<<1)//枚舉點{FOR(j, i+1, n*2-1){if(zero(p[i].x-p[j].x) && zero(p[i].y-p[j].y))continue;int find = 1;FOR(k, 0, n-1)//枚舉線段{if(!intersection_seg(l[k].a, l[k].b, p[i], p[j])){find = 0;break;}}if(find) return true;}}return false;}
寫這東西拖了兩天,不爽....各種事情..各種欠賬...