計算幾何總結


凸包

1、找到最左面的點,如有多個,取最下面的的點。
2、以這個點為原點,將所有點按與x軸正半軸的夾角為第一關鍵字(從,順時針),到原點距離為第二關鍵字(從)排序。

比較的代碼:

int left(SVe a,SVe b)
{
	return sgn(a*b);
}
int cmp(const void*a,const void *b)
{
	int t=left(*(SPt*)a-o,*(SPt*)b-o);
	if(t!=0)
		return t;
	return sgn(getlen(*(SPt*)a-o)-getlen(*(SPt*)b-o));
}

3、用棧維護,保證后一個向量在前一個的右面。

o=sz[0];
for(int i=1;i<n;i++)
{
	if(sgn(sz[i].x-o.x)==-1||(sgn(sz[i].x-o.x)==0&&sgn(sz[i].y-o.y)==-1))
		o=sz[i];
}
qsort(sz,n,sizeof(SPt),cmp);
st[tp++]=o;
for(int i=1;i<n;i++)
{
	while(tp>=2&&left(st[tp-1]-st[tp-2],sz[i]-st[tp-1])==1)
		tp-=1;
	st[tp++]=sz[i];
}

帶曲線的凸包:[SHOI2012]信用卡凸包

通過平移,可以發現:所有的曲線正好形成一個圓,所有直線分別向內平移可以形成一個完整凸包。
因此,求出所有圓心組成的凸包即可。
再加上一個圓就是答案。

極角排序

atan2(y,x);

從x軸負半軸開始,時針為增加,從\(-π到π\)
處理時可以類似環的處理方法,將范圍變為2倍,從\(-π到3π\)

旋轉卡殼

可以求凸包上的最值等問題。
例如:最遠點對,最小外界矩形,最大四邊形面積等。

對於最遠點對,就是先枚舉一條邊,然后用與其平行的直線切割凸包的另一面。
如果按照順時針枚舉這條邊,那么另一個點也是順時針轉動的,可以使用雙指針。

平行的切線可以使用叉積判斷(面積最大)。

最遠點對

double getmax(int n,SPt sz[50010])
{
	double ma=0;
	for(int i=0,j=1/*注意此處*/;i<n;i++)
	{
		while(mianj(sz[(j+1)%n],sz[i],sz[(i+1)%n])>mianj(sz[j],sz[i],sz[(i+1)%n]))
			j=(j+1)%n;
		double t=getlen(sz[i]-sz[j]);
		if(t>ma)ma=t;
		t=getlen(sz[(i+1)%n]-sz[(j+1)%n]);//要判斷兩種情況
		if(t>ma)ma=t;
	}
	return ma;
}

最小外接矩形

平行的邊和之前一樣。
與枚舉的邊垂直的邊可以使用點積判斷(最大化點的投影到端點的距離)。
注意只能垂直於枚舉邊,不能用對面的邊(對面的與凸包僅有一個交點)。

double getans(int n,SPt sz[50010],SPt dd[5])
{
	double ma=1e100;sz[n]=sz[0];
	for(int i=0,j=1,a=1,b=1;i<n;i++)
	{
		while(mianj(sz[j+1],sz[i],sz[i+1])>=mianj(sz[j],sz[i],sz[i+1]))
			j=(j+1)%n;
		while(dianj(sz[a+1]-sz[i],sz[i+1]-sz[i])>=dianj(sz[a]-sz[i],sz[i+1]-sz[i]))
			a=(a+1)%n;
		if(i==0)b=j;
		while(dianj(sz[b+1]-sz[i],sz[i]-sz[i+1])>=dianj(sz[b]-sz[i],sz[i]-sz[i+1]))
			b=(b+1)%n;
		SPt A=getH(sz[i],sz[i+1]-sz[i],sz[a]);
		SPt B=getH(A,getcz(sz[i+1]-sz[i]),sz[j]);
		SPt C=getH(B,getcz(A-B),sz[b]);
		SPt D=getH(sz[i],sz[i+1]-sz[i],sz[b]);
		double S=fabs(mianj(A,B,D));
		if(S<ma)
		{
			dd[0]=D;dd[1]=C;
			dd[2]=B;dd[3]=A;
			ma=S;
		}
	}
	return ma;
}

最小外接圓

有如下步驟:
首先,隨機打亂,防止被卡。
將點按照1~n的順序依次添加。
若當前點i在圓內,則跳過這個點。
若i不在圓內,那么這個點一定在外接圓上,以這個點為圓心,半徑為0作為新圓。
枚舉1~i-1,若j在圓內則跳過,否則以i,j為直徑作為新圓。
枚舉1~j-1,若k在圓內則跳過,否則用i,j,k確定一個新圓。
其實就是暴力,但期望\(O(n)\)

代碼

SCr c(sz[0],0);
for(int i=1;i<n;i++)
{
	if(inc(c,sz[i]))
		continue;
	c=SCr(sz[i],0);
	for(int j=0;j<i;j++)
	{
		if(inc(c,sz[j]))
			continue;
		c=SCr(mid(sz[i],sz[j]),getle2(sz[i]-sz[j])/4);//為了方便,用的是平方
		for(int k=0;k<j;k++)
		{
			if(!inc(c,sz[k]))
				c=getcr(sz[i],sz[j],sz[k]);
		}
	}
}

半平面交

就是給出若干半平面,求交集。(一定是凸多邊形)
用途很多,比如:

求一些凸多邊形的交集 [CQOI2006]凸多邊形
求二元一次不等式組的解集 [SCOI2015]小凸想跑步
求多邊形的核:即多邊形內的一個點集,點集內任意一點與多邊形邊上任意一點的連線都在多邊形內。

求法(假設在直線左面):

首先,按照極角從排序,平行的線,靠左的排前面。

struct SLi
{
	SPt s,t;double ji;
	SLi(){}
	SLi(SPt S,SPt T)
	{
		s=S;t=T;
		ji=atan2(t.y-s.y,t.x-s.x);
	}
};
int cmp(const void*a2,const void*b2)
{
	SLi a=*(SLi*)a2,b=*(SLi*)b2;
	int t=sgn(a.ji-b.ji);
	if(t!=0)return t;
	return right(b,a.s);
}

然后,依次添加,和之前平行的舍去。
維護兩個雙端隊列,一個保存直線,一個保存交點。
添加分三步:
1、從隊,把交點在當前直線右面的交點去掉,同時也去掉隊直線。
2、從隊,把交點在當前直線右面的交點去掉,同時也去掉隊直線。
3、添加當前直線,添加該直線和隊列中上一條直線的交點。

最后,用隊首直線排除隊尾直線,方法同之前。

代碼

double bpmj(SLi sz[200010],int n)
{
	qsort(sz,n,sizeof(SLi),cmp);
	int he=0,ta=0;
	for(int i=0;i<n;i++)
	{
		if(i>0&&pingx(sz[i],sz[i-1]))
			continue;
		while(he+1<ta&&right(sz[i],jd[ta-1])==1)
			ta-=1;
		while(he+1<ta&&right(sz[i],jd[he+1])==1)
			he+=1;
		if(he<ta)
			jd[ta]=jiaod(sz[i],dl[ta-1]);
		dl[ta++]=sz[i];
	}
	while(he+1<ta&&right(dl[he],jd[ta-1])==1)
		ta-=1;
	jd[he]=jiaod(dl[he],dl[ta-1]);
	jd[ta]=jd[he];
	double ans=0;
	for(int i=he;i<ta;i++)
		ans+=jd[i]*jd[i+1];
	return ans/2;
}

對於不等式\(ax+by+c\geq0\),也可以轉化為直線。

SLi::SLi(ll a,ll b,ll c)
{
	SPt s0,t0;
	if(b==0)
	{
		s0=SPt(double(-c)/a,0);
		t0=SPt(double(-c)/a,1);
	}
	else
	{
		s0=SPt(0,double(-c)/b);
		t0=SPt(1,double(-c-a)/b);
	}
	SPt jc=s0;
	if(a>0)jc.x+=1;
	else jc.x-=1;
	if(b>0)jc.y+=1;
	else jc.y-=1;
        //似乎是糟糕的做法?
	if(right(SLi(s0,t0),jc)==-1)
		*this=SLi(s0,t0);
	else
		*this=SLi(t0,s0);
}

Simpson積分

用途:求面積時,若組合圖形不易分割,但可以算出每個橫坐標上的縱坐標值,就可用此方法。
例題 [NOI2005]月下檸檬樹

基本思想就是把復雜的函數近似成二次函數。
先考慮二次函數的積分:

這樣子直接計算我們發現誤差非常大,畢竟原圖像可能不能很好的用二次函數逼近
所以,有自適應 Simpson 積分:

發現當前區間分成兩份和不分相差不大時,直接返回,否則分兩半遞歸。

代碼

double sim(double a,double b)
{
	return (b-a)*(F(a)+F(b)+4*F((a+b)/2))/6;
}
double asr(double a,double b,double z)
{
	double m=(a+b)/2,cl=sim(a,m),cr=sim(m,b);
	if(fabs(cl+cr-z)<eps)
		return z;
	return asr(a,m,cl)+asr(m,b,cr);
}

其他常用操作

向量運算

struct SPt
{
	double x,y;
	SPt(){}
	SPt(double X,double Y)
	{
		x=X;y=Y;
	}
};
typedef SPt SVe;
int sgn(double t)//符號
{	
	if(t>eps)return 1;
	else if(t<-eps)
		return -1;
	else return 0;
}
SVe operator-(SPt a,SPt b)
{
	return SVe(a.x-b.x,a.y-b.y);
}
SVe operator*(SVe a,double b)//叉積
{
	return SVe(a.x*b,a.y*b);
}
SPt operator+(SPt a,SVe b)
{
	return SPt(a.x+b.x,a.y+b.y);
}
double operator*(SVe a,SVe b)
{
	return a.x*b.y-a.y*b.x;
}
double dianj(SPt a,SPt b)//點積
{
	return a.x*b.x+a.y*b.y;
}
SVe getcz(SVe a)//垂直向量
{
	return SVe(a.y,-a.x);
}
double getlen(SVe a)
{
	return sqrt(a.x*a.x+a.y*a.y);
}

求交點

SPt jiaod(SPt a,SVe va,SPt b,SVe vb)
{
	double t=((b-a)*vb)/(va*vb);
	return a+va*t;
}

求垂足(投影)

SPt getH(SPt O,SVe a,SPt N)
{
	double t=dianj(a,N-O)/dianj(a,a);
	SPt M;
	M.x=O.x+a.x*t;
	M.y=O.y+a.y*t;
	return M;
}

旋轉

SVe rotate(SVe a,double r)
{
	double tx=cos(r),ty=sin(r);
	SVe rt;
	rt.x=a.x*tx-a.y*ty;
	rt.y=a.x*ty+a.y*tx;
	return rt;
}

三點確定一個圓

struct SCr
{
	SPt o;
	double r;
	SCr(){}
	SCr(SPt O,double R)
	{
		o=O;r=R;
	}
};
SCr getcr(SPt a,SPt b,SPt c)
{
	SPt p1=mid(a,b),p2=mid(b,c);
	SPt o=jiaod(p1,getcz(p1-a),p2,getcz(p2-b));
	return SCr(o,getle2(o-a));
}

求公切線(一條)

利用相似

for(int i=0;i<n;i++)
{
	double d=x[i+1]-x[i],a=r[i+1]-r[i],c=sqrt(d*d-a*a);
	double x0=x[i]-r[i]/d*a,y0=r[i]/d*c;
	double x1=x[i+1]-r[i+1]/d*a,y1=r[i+1]/d*c;
	k[i]=(y1-y0)/(x1-x0);
	b[i]=y0-k[i]*x0;
}

求出所有公切線

以一個圓心為原點,將坐標進行變換,另一個圓心放到x軸上。
就可以類似上一個的方法了,還是相似。
最后別忘變換回去。

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 
#define eps 1e-6 
struct SPt {
	double x,y;
	SPt() {}
	SPt(double X, double Y) {
		x = X;y = Y;
	}
};
SPt rotate(SPt a, double co, double si) {
	SPt rt;
	rt.x = a.x * co - a.y * si;
	rt.y = a.x * si + a.y * co;
	return rt;
}
struct SLi {
	SPt s,t;
};
void geta2(double r1, double d, double r2, SLi & l1, SLi & l2) {
	double t = r2 - r1,l = sqrt(d * d - t * t);
	double si = l / d,co = t / d;
	l1.s = SPt( - r1 * co, r1 * si);
	l1.t = SPt(d - r2 * co, r2 * si);
	l2.s = SPt( - r1 * co, -r1 * si);
	l2.t = SPt(d - r2 * co, -r2 * si);
}
void getb2(double r1, double d, double r2, SLi & l1, SLi & l2) {
	double t = r2 + r1,l = sqrt(d * d - t * t);
	double si = l / d,co = t / d;
	l1.s = SPt(r1 * co, -r1 * si);
	l1.t = SPt(d - r2 * co, r2 * si);
	l2.s = SPt(r1 * co, r1 * si);
	l2.t = SPt(d - r2 * co, -r2 * si);
}
int getjd(double r1, double d, double r2, SLi li[5]) {
	if (fabs(d) < eps && fabs(r1 - r2) < eps) return - 1;
	double a1 = -r1,b1 = r1,a2 = d - r2,b2 = d + r2;
	if (a1 - a2 > eps || b1 - b2 > eps) return 0;
	if (fabs(a1 - a2) < eps) {
		li[0].s = li[0].t = SPt( - r1, 0);
		return 1;
	}
	if (fabs(b1 - b2) < eps) {
		li[0].s = li[0].t = SPt(r1, 0);
		return 1;
	}
	geta2(r1, d, r2, li[0], li[1]);
	if (b1 - a2 > eps) return 2;
	if (fabs(b1 - a2) < eps) {
		li[2].s = li[2].t = SPt(r1, 0);
		return 3;
	}
	getb2(r1, d, r2, li[2], li[3]);
	return 4;
}
SLi li[5];
int sgn(double x) {
	if (x > eps) return 1;
	if (x < -eps) return - 1;
	return 0;
}
int cmp(const void * a, const void * b) {
	int x = sgn(((SLi * ) a) ->s.x - ((SLi * ) b) ->s.x);
	int y = sgn(((SLi * ) a) ->s.y - ((SLi * ) b) ->s.y);
	if (x != 0) return x;
	return y;
}
int main() {
	while (1) {
		double x1,y1,r1,x2,y2,r2;
		scanf("%lf%lf%lf%lf%lf%lf", &x1, &y1, &r1, &x2, &y2, &r2);
		if (fabs(r1) < eps) break;
		double d = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
		double a = x2 - x1,b = y2 - y1,co = a / d,si = b / d;
		int n = getjd(r1, d, r2, li);
		printf("%d\n", n);
		if (n == -1) n = 0;
		for (int i = 0; i < n; i++) {
			li[i].s = rotate(li[i].s, co, si);
			li[i].t = rotate(li[i].t, co, si);
		}
		qsort(li, n, sizeof(SLi), cmp);
		for (int i = 0; i < n; i++) {
			double a = li[i].s.x - li[i].t.x;
			double b = li[i].s.y - li[i].t.y;
			printf("%.5lf %.5lf %.5lf %.5lf %.5lf\n", li[i].s.x + x1, li[i].s.y + y1, li[i].t.x + x1, li[i].t.y + y1, sqrt(a * a + b * b));
		}
	}
	return 0;
}

注意事項

1、若比較的符號帶等於,則需要使用eps判斷。
2、注意輸出-0的情況。

void output(double x)
{
	if(fabs(x)<eps)
		x=0;
	printf("%.5lf ",x);
}

3、若出現奇怪錯誤,可能是數組開小,eps過大/過小。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM