计算几何基础

计算几何基础

计算几何学了一些,感觉学的太乱,整理一下。


精度:

由于使用浮点数,所以会产生精度问题,所以在比较大小的时候不能直接采用等于。

const double eps=1e-8;
int sgn(double x)
{
    if(fabs(x) < eps)return 0;
    if(x < 0) return -1;
    else return 1;
}

三角函数和角度转换:

c语言中自带三角函数 s i n ( x ) , c o s ( x ) , t a n ( x ) 。 x sin(x),cos(x),tan(x)。x sin(x),cos(x),tan(x)x是以弧度为单位。
假设输入得角度为 θ ° \theta° θ°,那么: x = θ π 180 x=\frac{\theta\pi}{180} x=180θπ为了精确,采用: π = a c o s ( − 1 ) \pi=acos(-1) π=acos(1)
还有经常使用得一个函数atan2(double y,double x)。返回值为 θ = a r c t a n y x \begin{aligned}\theta =arctan\frac{y}{x}\end{aligned} θ=arctanxy

点、线、向量:

struct point
{
    double x,y;
    point(){}
    point(double xx,double yy){x=xx;y=yy;}
    point operator +(point b){return point(x+b.x,y+b.y);}
    point operator -(point b){return point(x-b.x,y-b.y);}
    double operator ^(point b){return x*b.y-y*b.x;}//叉乘 
    double operator *(point b){return x*b.x+y*b.y;}//点乘 
    point operator *(double b){return point(x*b,y*b);}//数乘 
    double gettan(){return atan2(y,x);}//角度  
}; 
struct line
{
    point s,e,l;
    line(){}
    line(point ss,point ee){s = ss;e = ee;l=e-s;}
    double gettan(){return atan2(e.y-s.y,e.x-s.x);}
};

叉乘(叉积):

a ⃗ × b ⃗ = ∣ a ⃗ ∣ ∣ b ⃗ ∣ s i n θ = x a × y b − y a × x b \vec a\times\vec b=|\vec a||\vec b|sin\theta=x_a\times y_b-y_a\times x_b a ×b =a b sinθ=xa×ybya×xb
如果 b ⃗ \vec b b a ⃗ \vec a a 的逆时针方向,值为正;顺时针方向值为负;共线为0。
叉积也可以用来表示这两条向量构成的平行四边形的有向面积。

hdu多校第八场1003Clockwise or Counterclockwise就是用叉积判断 A B → \overrightarrow {AB} AB A C → \overrightarrow {AC} AC 的位置关系。

向量旋转:

//a为弧度
point spin(point p,double a)
{
	return point(p.x*cos(a)-p.y*sin(a),p.y*cos(a)+p.x*sin(a));
} 

a ⃗ = ( x , y ) \vec a=(x,y) a =(x,y),倾斜角为 θ \theta θ,则长度为 l = x 2 + y 2 l=\sqrt{x^2+y^2} l=x2+y2 ,所以 x = l c o s θ , y = l s i n θ x=lcos\theta,y=lsin\theta x=lcosθ,y=lsinθ。逆时针旋转 α \alpha α度角得到 b ⃗ \vec b b
b ⃗ = ( l c o s ( θ + α ) , l s i n ( θ + α ) ) = ( l ( c o s θ c o s α − s i n θ s i n α ) , l ( s i n θ c o s α + c o s θ s i n α ) ) = ( l c o s θ c o s α − l s i n θ s i n α , l s i n θ c o s α + l c o s θ s i n α ) = ( x c o s α − y s i n α , y c o s α + x s i n α ) \begin{aligned} \vec b&=(lcos(\theta+\alpha),lsin(\theta+\alpha))\\&=(l(cos\theta cos\alpha -sin\theta sin\alpha),l(sin\theta cos\alpha+cos\theta sin\alpha ))\\&=(lcos\theta cos\alpha -lsin\theta sin\alpha,lsin\theta cos\alpha+lcos\theta sin\alpha)\\&=(xcos\alpha -ysin\alpha,ycos\alpha+xsin\alpha)\end{aligned} b =(lcos(θ+α),lsin(θ+α))=(l(cosθcosαsinθsinα),l(sinθcosα+cosθsinα))=(lcosθcosαlsinθsinα,lsinθcosα+lcosθsinα)=(xcosαysinα,ycosα+xsinα)

点到直线的投影点:

point projection(line l,point p)
{
	return l.s+l.l*((l.l*(p-l.s))/((l.l)*(l.l)));
} 

用点积来求。
C C C到直线 A B AB AB的投影点为 C 1 C_1 C1
∵ A B → ⋅ A C → = ∣ A B → ∣ ∣ A C → ∣ c o s θ = ∣ A B → ∣ ∣ A C 1 → ∣ \because \overrightarrow{AB}\cdot\overrightarrow{AC}=|\overrightarrow{AB}||\overrightarrow{AC}|cos\theta=|\overrightarrow{AB}||\overrightarrow{AC_1}| AB AC =AB AC cosθ=AB AC1
∴ ∣ A C 1 → ∣ = A B → ⋅ A C → ∣ A B → ∣ \begin{aligned}\therefore |\overrightarrow{AC_1}|=\frac{\overrightarrow{AB}\cdot\overrightarrow{AC}}{|\overrightarrow{AB}|}\end{aligned} AC1 =AB AB AC
∵ O C 1 → = O A → + A C 1 → A C 1 → = ∣ A C 1 → ∣ ∣ A B → ∣ × A B → \begin{aligned}\because &\overrightarrow{OC_1}=\overrightarrow{OA}+\overrightarrow{AC_1}\\&\overrightarrow{AC_1}=\frac{|\overrightarrow{AC_1}|}{|\overrightarrow{AB}|}\times \overrightarrow{AB}\end{aligned} OC1 =OA +AC1 AC1 =AB AC1 ×AB
∴ O C 1 → = O A → + ∣ A C 1 → ∣ ∣ A B → ∣ × A B → = O A → + A B → ⋅ A C → ∣ A B → ∣ ∣ A B → ∣ × A B → = O A → + A B → ⋅ A C → ∣ A B → ∣ 2 × A B → \begin{aligned}\therefore \overrightarrow{OC_1}=\overrightarrow{OA}+\frac{|\overrightarrow{AC_1}|}{|\overrightarrow{AB}|}\times \overrightarrow{AB}=\overrightarrow{OA}+\frac{\frac{\overrightarrow{AB}\cdot\overrightarrow{AC}}{|\overrightarrow{AB}|}}{|\overrightarrow{AB}|}\times\overrightarrow{AB} =\overrightarrow{OA}+\frac{\overrightarrow{AB}\cdot\overrightarrow{AC}}{|\overrightarrow{AB}|^2}\times\overrightarrow{AB}\end{aligned} OC1 =OA +AB AC1 ×AB =OA +AB AB AB AC ×AB =OA +AB 2AB AC ×AB

计算两点间距离:

double dis(point a,point b)
{
	return sqrt((a-b)*(a-b));
}

判断点在线段上:

bool onseg(line l,point p)
{
	bool x1=sgn((l.s-p)^(l.e-p))==0;
	bool x2=sgn((l.s-p)*(l.e-p))<=0;
	return x1&&x2;
}

因为点 P P P在线段 A B AB AB上,由叉积的性质可以得到 P A → × P B → = 0 \overrightarrow{PA}\times\overrightarrow{PB}=0 PA ×PB =0。但是如果 P P P A B AB AB的延长线上,这也符合。所以要看 P A → ⋅ P B → < = 0 \overrightarrow{PA}\cdot\overrightarrow{PB}<=0 PA PB <=0是否成立,因为此时 ∠ A P B = 180 ° , c o s 180 ° = − 1 ∠APB=180°,cos180°=-1 APB=180°,cos180°=1,所以点乘的值为负。

计算点到直线距离:

double dis_pl(point p,line l)
{
	point v=p-l.s;
	double ans=(v^l.l)/sqrt(l.l*l.l);
	return fabs(ans);
}

以前都学过 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) A x + B y + C = 0 Ax+By+C=0 Ax+By+C=0的距离为 d = ∣ A x 0 + B y 0 + C ∣ A 2 + B 2 \begin{aligned}d=\frac{|Ax_0+By_0+C|}{\sqrt{A^2+B^2}}\end{aligned} d=A2+B2 Ax0+By0+C。但是这么计算比较复杂,我们还是通过叉乘和点乘来计算。
我们要求点 P P P到直线 A B AB AB的距离。
∵ S Δ A B P = ∣ A P → × A B → 2 ∣ = d × ∣ A B → ∣ 2 \begin{aligned}\because S_{\Delta ABP}=\left|\frac{\overrightarrow{AP}\times\overrightarrow{AB}}{2}\right|=\frac{d\times |\overrightarrow{AB}|}{2}\end{aligned} SΔABP=2AP ×AB =2d×AB
∴ d = ∣ A P → × A B → ∣ A B → ∣ ∣ \begin{aligned}\therefore d=\left|\frac{\overrightarrow{AP}\times\overrightarrow{AB}}{|\overrightarrow{AB}|}\right|\end{aligned} d=AB AP ×AB
如图,
2 S Δ A B P = ∣ A P → × A B → ∣ = ∣ ( − 2 , 2 ) × ( 2 , 1 ) ∣ = ∣ − 6 ∣ = 6 \begin{aligned}2S_{\Delta ABP}=|\overrightarrow{AP}\times\overrightarrow{AB}|=|(-2,2)\times(2,1)|=|-6|=6\end{aligned} 2SΔABP=AP ×AB =(2,2)×(2,1)=6=6
∣ A B → ∣ = x A B 2 + y A B 2 = 5 ≈ 2.23607 \begin{aligned}|\overrightarrow{AB}|=\sqrt{x_{AB}^2+y_{AB}^2}=\sqrt{5}\approx2.23607\end{aligned} AB =xAB2+yAB2 =5 2.23607
d = ∣ A P → × A B → ∣ A B → ∣ ∣ = 6 5 ≈ 2.82843 \begin{aligned}d=\left|\frac{\overrightarrow{AP}\times\overrightarrow{AB}}{|\overrightarrow{AB}|}\right|=\frac{6}{\sqrt{5}}\approx2.82843\end{aligned} d=AB AP ×AB =5 62.82843
在这里插入图片描述

判断线段相交:

bool judge(line l1,line l2)
{
	point a=l1.s,b=l1.e,c=l2.s,d=l2.e;
	double x1=((c-a)^l1.l)*((d-a)^l1.l);
	double x2=((a-c)^l2.l)*((b-c)^l2.l);
	x1&&x2&&
		max(l1.s.x,l1.e.x) >= min(l2.s.x,l2.e.x) &&
        max(l2.s.x,l2.e.x) >= min(l1.s.x,l1.e.x) &&
        max(l1.s.y,l1.e.y) >= min(l2.s.y,l2.e.y) &&
        max(l2.s.y,l2.e.y) >= min(l1.s.y,l1.e.y);
}

如果有两个线段AB和CD,判断相交就是判断一条线段的两个端点是否在另外一条线段的两端。也就是判断A,B是否在CD两端以及C,D是否在AB两端。
如果A,B在CD两端,那么 ( C A → × C D → ) ⋅ ( C B → × C D → ) < 0 (\overrightarrow {CA}\times\overrightarrow{CD})\cdot(\overrightarrow{CB}\times\overrightarrow{CD})<0 (CA ×CD )(CB ×CD )<0,因为如果A,B在CD两侧,那么CA,CB肯定分别在CD的顺时针和逆时针方向,这时候用叉积即可判断。由于平行或者点重合的情况存在,所以要特判一下叉积为0时, C , D C,D C,D点是否在 A B AB AB上,或者 A , B A,B A,B点是否在 C D CD CD上。
题目:HDU1086

计算两直线交点:

point getcross(line l1,line l2)
{
	double s1=((l2.s-l1.s)^(l2.e-l1.s));
	double s2=((l2.e-l1.e)^(l2.s-l1.e));
	if(s1+s2==0)//平行
		return point{inf,inf};
	return l1.s+l1.l*(s1/(s1+s2));
}

如图:
A B AB AB C D CD CD交点为 E E E
用叉积计算出 S Δ A C D S_{\Delta ACD} SΔACD S Δ B C D S_{\Delta BCD} SΔBCD的有向面积的值 S 1 , S 2 S_1,S_2 S1,S2。做垂线 A H 1 , B H 2 AH_1,BH_2 AH1,BH2
易得: S 1 = ∣ C D ∣ × ∣ A H 1 ∣ 2 , S 2 = ∣ C D ∣ × ∣ B H 2 ∣ 2 \begin{aligned}S_1=\frac{|CD|\times|AH_1|}{2},S_2=\frac{|CD|\times|BH_2|}{2}\end{aligned} S1=2CD×AH1,S2=2CD×BH2
∴ S 1 S 2 = A H 1 B H 2 \begin{aligned}\therefore \frac{S_1}{S_2}=\frac{AH_1}{BH_2}\end{aligned} S2S1=BH2AH1
易证: Δ A H 1 E ∼ Δ B H 2 E \Delta AH_1E\sim\Delta BH_2E ΔAH1EΔBH2E
∴ S 1 S 1 + S 2 = A H 1 A H 1 + B H 2 = A E A E + B E = A E A B \begin{aligned}\therefore\frac{S_1}{S_1+S_2}=\frac{AH_1}{AH_1+BH_2}=\frac{AE} {AE+BE}=\frac{AE}{AB}\end{aligned} S1+S2S1=AH1+BH2AH1=AE+BEAE=ABAE
∴ O E → = O A → + S 1 S 1 + S 2 A B → \begin{aligned}\therefore\overrightarrow{OE}=\overrightarrow{OA}+\frac{S_1}{S_1+S_2}\overrightarrow{AB}\end{aligned} OE =OA +S1+S2S1AB
这样就得到了 E E E的坐标了。
在这里插入图片描述

三角形面积:

  1. 两边叉积除以2取绝对值。
  2. 海伦公式:
    S = p ( p − a ) ( p − b ) ( p − c ) S=\sqrt{p(p-a)(p-b)(p-c)} S=p(pa)(pb)(pc)
    p = a + b + c 2 \begin{aligned}p=\frac{a+b+c}{2}\end{aligned} p=2a+b+c

Pick定理:

如果一个多边形的顶点在格点上,那么他的面积可以表示为:
S = a + b 2 − 1 S=a+\frac{b}{2}-1 S=a+2b1
a a a表示多边形内部的点数, b b b表示多边形落在格点边界上的点数。

多边形面积:

	point d[1000];
	int n,i;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		scanf("%lf%lf",&d[i].x,&d[i].y);
	d[n+1]=d[1];
	double area=0;
	for(i=1;i<=n;i++)
		area+=(d[i]^d[i+1]);
	area/=2;
	printf("%.2lf",fabs(area));

利用叉积(三角形剖分)进行计算。
如下图,
S A B C D E F = S Δ O A F + S Δ O B A + S Δ O C B + S Δ O D C − S Δ O E D − S Δ O F E S_{ABCDEF}=S_{\Delta OAF}+S_{\Delta OBA}+S_{\Delta OCB}+S_{\Delta ODC}-S_{\Delta OED}-S_{\Delta OFE} SABCDEF=SΔOAF+SΔOBA+SΔOCB+SΔODCSΔOEDSΔOFE
由于叉积有方向,我们可以发现叉积之和除以2就是多边形的面积。这样我们只要逆时针求相邻点的叉积即可,多边形面积就可以表示为:
S = O A n → × O A 1 → + ∑ i = 1 n − 1 O A i → × O A i + 1 → 2 \begin{aligned}S=\frac{\overrightarrow{OA_n}\times\overrightarrow{OA_1}+\sum_{i=1}^{n-1}\overrightarrow{OA_i}\times\overrightarrow{OA_{i+1}}}{2}\end{aligned} S=2OAn ×OA1 +i=1n1OAi ×OAi+1
在这里插入图片描述

题目:HDU2036

凸包:

凸包可以理解为包含所有点得凸多边形,或者形象得想成能包围所有点得橡皮筋,也可以是说一个周长最小,能包含住所有点得多边形。
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

//graham扫描法 O(nlogn)
#include<bits/stdc++.h>
#define ll long long
#define cl(x,y) memset(x,y,sizeof(x))
const int N=1e6+10;
const double eps=1e-8;
const double pi=acos(-1);
using namespace std;
int n,top;
int sgn(double x)
{
    if(fabs(x) < eps)return 0;
    if(x < 0) return -1;
    else return 1;
}
struct point
{
    double x,y;
    point(){}
    point(double xx,double yy){x=xx;y=yy;}
    point operator +(point b){return point(x+b.x,y+b.y);}
    point operator -(point b){return point(x-b.x,y-b.y);}
    double operator ^(point b){return x*b.y-y*b.x;}//叉乘 
    double operator *(point b){return x*b.x+y*b.y;}//点乘 
    point operator *(double b){return point(x*b,y*b);}//数乘   
    void read(){scanf("%lf%lf",&x,&y);}
}p[N],sta[N];
int cmp1(point a,point b)
{
	if(a.y==b.y)
		return a.x<b.x;
	else
		return a.y<b.y;
}
int cmp2(point a,point b)//极角排序 
{
	if(atan2(a.y-sta[1].y,a.x-sta[1].x)!=atan2(b.y-sta[1].y,b.x-sta[1].x))
        return (atan2(a.y-sta[1].y,a.x-sta[1].x))<(atan2(b.y-sta[1].y,b.x-sta[1].x));
    return a.x<b.x;
}
double dis(point a,point b)
{
	return sqrt((a-b)*(a-b));
}
void graham()
{
	int i;
	sort(p+1,p+n+1,cmp1);
	sta[1]=p[1];
	sort(p+2,p+n+1,cmp2);
	sta[2]=p[2];
	top=2;
	for(i=3;i<=n;i++)
	{
		while(top>1 && ((sta[top]-sta[top-1])^(p[i]-sta[top-1]))<0)
			top--;
		sta[++top]=p[i];
	}
	return;
}
int main()
{
	int i;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		p[i].read();
	graham();
	//计算凸包周长 
	double s=0;
	for(i=2;i<=top;i++)
		s+=dis(sta[i],sta[i-1]);
	s+=dis(sta[top],sta[1]);
	//计算凸包面积
	double area=0;
	for(i=3;i<=top;i++)
		area+=((sta[i-1]-sta[1])^(sta[i]-sta[1]));
	cout<<s<<endl<<area<<endl;
	return 0;
}

旋转卡壳:

时间复杂度 O ( n ) O(n) O(n)

求凸多边形直径(凸包上最远点对的距离):

double rotating_calipers()
{
	sta[top+1]=sta[1];
	int now=3,i;
	double ans=0;
	for(i=1;i<=top;i++)
	{
		while(((sta[i+1]-sta[i])^(sta[now]-sta[i]))<((sta[i+1]-sta[i])^(sta[now+1]-sta[i])))
		{
			now++;
			if(now==top+1)
				now=1;
		}
		ans=max(ans,dis(sta[now],sta[i]));
	}
	return ans;
}

求凸包的宽:

double rc()
{
	sta[top+1]=sta[1];
	int now=3,i;
	double ans=INF;
	for(i=1;i<=top;i++)
	{
		while(((sta[i+1]-sta[i])^(sta[now]-sta[i]))<((sta[i+1]-sta[i])^(sta[now+1]-sta[i])))
		{
			now++;
			if(now==top+1)
				now=1;
		}
		ans=min(ans,((sta[i+1]-sta[i])^(sta[now]-sta[i]))/dis(sta[i],sta[i+1]));
	}
	return ans;
}

半平面交:

半平面是指一条直线会把平面分成两半,我们一般把直线左边的半平面视为需要的半平面。
求多边形的核(从这个区域可以看到多边形的任意位置)。
思路:

  1. 极角排序。
  2. 去掉极角相同时,取左边的边。因为显然右边线的半平面包含左边线的半平面,加上左边的线右边的线肯定要被舍弃。
  3. 维护双向队列,看加入一条线之后,头尾部线相交的交点是否在这条线的右侧,如果在,弹出线。
  4. 得到的线的集合就是要求的半平面交。
int cmp(line a,line b)
{
	double t1=a.gettan(),t2=b.gettan();
	if(sgn(t1-t2)==0)
		return (a.l^(b.e-a.s))>0;
	return t1<t2;
}
//判断bc交点是否在a的右边,如果在,去掉头部或者尾部的线 
int onright(line a,line b,line c)
{
	point p=getcross(b,c);
	return (a.l^(p-a.s))<0;
} 
//半平面交 
void hpi(line li[],int n)
{
	sort(li+1,li+n+1,cmp);
	int l=1,r=1,cnt=0,i;//模拟deque
	line que[N];
	//去重,极角相同取左边,即保留最后直线 
	for(i=1;i<n;i++)
		if(sgn(li[i].gettan()-li[i+1].gettan()))
			li[++cnt]=li[i];
	li[++cnt]=li[n];
	//判断新加入线的影响 
	for(i=1;i<=cnt;i++)
	{
		while(r-l>1 && onright(li[i],que[r-1],que[r-2]))
			r--;
		while(r-l>1 && onright(li[i],que[l],que[l+1]))
			l++;
		que[r++]=li[i]; 
	}
	//判断最先和最后加入的直线的影响
	while(r-l>1 && onright(que[l],que[r-1],que[r-2]))
		r--;
	while(r-l>1 && onright(que[r-1],que[l],que[l+1]))
		l++;
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值