快乐暑假(九)——计算几何的编程实验

计算几何学是研究几何问题的算法,在现代工程与数学,诸如计算机图形学、计算机辅助设计、机器人学都要应用计算几何学。因此计算几何学是算法体系中的一个重要组成部分。

点线面运算的实验


计算点积和叉积

点积:定义 A B ⃗ {\vec{AB} } AB ⋅ A B ⃗ {·\vec{AB} } AB 为 = ∣ A B ⃗ ∣ {|\vec{AB} |} AB * ∣ C D ⃗ ∣ {|\vec{CD}| } CD * c o s ( α ) {cos(α)} cos(α)

叉积: 定义 P 1 ⃗ {\vec{P_{1}} } P1 ^ P 2 ⃗ { \vec{ P_{2}}} P2 为向量 P 1 ⃗ {\vec{P_{1}} } P1 P 2 ⃗ {\vec{P_{2}} } P2 的叉积
在这里插入图片描述
P 1 ⃗ {\vec{P_{1}} } P1 ^ P 2 ⃗ = { \vec{P_{2}}=} P2 = ∣ x 1 y 1 x 2 y 2 ∣ = x 1 ∗ y 2 − x 2 ∗ y 1 = − P 2 ⃗ { \left| \begin{array}{ccc} x_{1} & y_{1} \\x_{2} & y_{2} \end{array} \right| = x_{1} * y_{2}-x_{2} * y_{1} = -\vec{P_{2}} } x1x2y1y2=x1y2x2y1=P2 ^ P 1 ⃗ { \vec{P_{1}}} P1

两向量的叉积的绝对值 ∣ P 1 ⃗ {|\vec{P_{1}} } P1 ^ P 2 ⃗ ∣ { \vec{P_{2}}|} P2 就是平行四边形{ 0 , p 1 , p 2 , p 1 + p 2 {0,p_{1},p_{2},p_{1}+p_{2}} 0,p1,p2,p1+p2}的面积。

叉积的几点性质:

逆时针叉积为正,顺时针叉积为负, p 0 {p_{0}} p0 p 1 {p_{1}} p1共线时,叉积为0。
在这里插入图片描述

叉积的应用:

  • 判断点是在直线的哪一边
    p 1 ⃗ {\vec{p_{1}}} p1 ^ p 2 ⃗ > 0 {\vec{p_{2}}>0} p2 >0时,证明 p 1 ⃗ {\vec{p_{1}}} p1 是在 p 2 ⃗ {\vec{p_{2}}} p2 的逆时针方向,也就是 p 2 ⃗ {\vec{p_{2}}} p2 所在直线的左方。

    p 1 ⃗ {\vec{p_{1}}} p1 ^ p 2 ⃗ &lt; 0 {\vec{p_{2}}&lt;0} p2 <0时,证明 p 1 ⃗ {\vec{p_{1}}} p1 是在 p 2 ⃗ {\vec{p_{2}}} p2 的顺时针方向,也就是 p 2 ⃗ {\vec{p_{2}}} p2 所在直线的右方。

    p 1 ⃗ {\vec{p_{1}}} p1 ^ p 2 ⃗ = 0 {\vec{p_{2}}=0} p2 =0时, p 1 ⃗ {\vec{p_{1}}} p1 p 2 ⃗ {\vec{p_{2}}} p2 共线。

例题: POJ 1106 Transmitters
代码

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const int Max = 1010; 
int x_0,y_0,n;
int x[Max],y[Max];
int ans = -1;
double r;
double mul(int i,int j){
	return 	(x[i]-x_0)*(y[j]-y_0) - (x[j]-x_0)*(y[i]-y_0);
}
double get_r(int x_,int y_){
	return sqrt((x_-x_0)*(x_-x_0)+(y_-y_0)*(y_-y_0));
}
int main(){
	while(scanf("%d %d %lf",&x_0,&y_0,&r)){
		ans = -1;
		if(r < 0) break;
		getchar();
		scanf("%d",&n);
		for(int i = 0 ; i < n; i++){
			getchar();
			scanf("%d %d",x+i,y+i);
		}
		for(int i = 0 ; i < n; i++){
			int cnt = 0;
			for(int j = 0; j < n; j++){
				 if(mul(i,j) <= 0 && get_r(x[j],y[j]) <= r){
				 	cnt++;
				 	//printf("x = %d y = %d p1^p2 = %lf\n ",i,j,mul(i,j));
				 }
			}
			ans = max(ans,cnt);
		}printf("%d\n",ans);
	}
	return 0;
} 
  • 计算多边形的面积

    我们可以利用叉积计算多边形的面积。设多边形的顶点按照顺时针方向(或逆时针)排列为 p 0 , p 1 , . . . , p n − 1 {p_{0},p{1},...,p_{n-1}} p0,p1,...,pn1为使多边形的边首尾相接,另设 p n = p 0 {p_{n} = p_{0}} pn=p0
    多边形面积

s = ∣ ∑ n = 1 n − 2 p i   ^ p i + 1 ∣ 2 s = \frac{|\sum_{n=1}^{n-2}p_{i}\hat{ \ } p_{i+1}|}{2} s=2n=1n2pi ^pi+1

例题: POJ 1654 Area
代码:

#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdlib> 
#include <algorithm>
#define ll long long 
using namespace std;
const int Max = 1e6+1000;
int dir[][2] = {{0,0},{1,-1},{1,0},{1,1},{0,-1},{0,0},{0,1},{-1,-1},{-1,0},{-1,1}};
int cas;
ll mul(ll x1,ll y1,ll x2,ll y2){
	ll res =  x1*y2-x2*y1; 
	return res;
}
int main(){
	cin>>cas;
	while(cas--){
		int cnt = 0;
		char c;
		int x_ = 0,y_ = 0;
		int x,y;
		ll ans = 0;
		while(cin>>c){
			if(c == '5')
				break;
			x = x_ + dir[c-'0'][0];
			y = y_ + dir[c-'0'][1];
			ans += mul(x_,y_,x,y);
			x_ = x;
			y_ = y;
			
			//cout<<mul(x_,y_,x,y)<<"\n";
		}
		if(ans < 0)
		ans = -ans;
		cout<<ans/2;
		if(ans % 2)
			cout<<".5";
		cout<<"\n";
	}
}
计算线段交

跨立: 如果两个线段相交的话,那么一条线段两边的两个点要位于另一条线段的两边。
在这里插入图片描述

判断两条直线是否相交:

使用叉积性质和跨立条件,可以得出结论:两个相互跨立的线段就是相交的。
在这里插入图片描述
例题: POJ 2653 Pick-up sticks

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
#define ll long long
using namespace std;

const int Max = 100010;
int n;
double x1[Max],y1[Max],x2[Max],y2[Max];
bool isinter(int i,int j){
	double res1 = (x1[j]-x1[i])*(y1[j]-y2[i]) - (x1[j]-x2[i])*(y1[j]-y1[i]);
	double res2 = (x2[j]-x1[i])*(y2[j]-y2[i]) - (x2[j]-x2[i])*(y2[j]-y1[i]);
	double res3 = (x1[j] - x1[i])*(y2[j]-y1[i]) - (x2[j]-x1[i])*(y1[j]-y1[i]);
	double res4 = (x1[j] - x2[i])*(y2[j]-y2[i]) - (x2[j] - x2[i])*(y1[j]-y2[i]);
	if(res1 * res2 <= 0 && res3 * res4 <= 0){//相交 
		return true; 
	}else {//不相交 
		return false;
	}
}

int main() {
	while(scanf("%d",&n) && n) {
		vector<int>  ans;  
		for(int i =1 ; i <= n; i++) {
			getchar();
			scanf("%lf%lf%lf%lf",x1+i,y1+i,x2+i,y2+i);
		}
		for(int i = 1; i < n; i++){
			int flag = 0;
			for(int j = i+1; j <= n; j++){
				if(isinter(i,j)){
					flag = 1; 
					break;
				} 
			}
			if(flag == 0){
				ans.push_back(i);
			}
		}
		int len = ans.size();
		printf("Top sticks: ");
		for(int i = 0 ; i < len; i++){
			printf("%d, ",ans[i]);
		}
		printf("%d.\n",n);
	}
	return 0;
}
/*

*/

在两条直线相交的情况下如何求交点:
结论和证明过程推导:
在这里插入图片描述
在这里插入图片描述对,就是最后这个公式,记下来就可以了。

例题: POJ 1269 Intersecting Lines

//抄的吴老师和kuangbin的模板
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
#include <set>
#include <string>
#include <cmath>

using namespace std;
const double eps = 1e-8;
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 _x,double _y)
    {
        x = _x;y = _y;
    }
    Point operator -(const Point &b)const
    {
        return Point(x - b.x,y - b.y);
    }
    double operator ^(const Point &b)const
    {
        return x*b.y - y*b.x;
    }
    double operator *(const Point &b)const
    {
        return x*b.x + y*b.y;
    }
};
struct Line
{
    Point s,e;
    Line(){}
    Line(Point _s,Point _e)
    {
        s = _s;e = _e;
    }
    pair<Point,int> operator &(const Line &b)const
    {
        Point res = s;
        if(sgn((s-e)^(b.s-b.e)) == 0)
        {
            if(sgn((b.s-s)^(b.e-s)) == 0)
                return make_pair(res,0);//两直线重合
            else return make_pair(res,1);//两直线平行
        }
        double t = ((s-b.s)^(b.s-b.e))/((s-e)^(b.s-b.e));
        res.x += (e.x - s.x)*t;
        res.y += (e.y - s.y)*t;
        return make_pair(res,2);//有交点
    }
};
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int T;
    scanf("%d",&T);
    double x1,y1,x2,y2,x3,y3,x4,y4;
    printf("INTERSECTING LINES OUTPUT\n");
    while(T--)
    {
        scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&x1,&y1,&x2,&y2,&x3,&y3,&x4,&y4);
        Line line1 = Line(Point(x1,y1),Point(x2,y2));
        Line line2 = Line(Point(x3,y3),Point(x4,y4));
        pair<Point,int> ans = line1 & line2;
        if( ans.second == 2)printf("POINT %.2lf %.2lf\n",ans.first.x,ans.first.y);
        else if(ans.second == 0)printf("LINE\n");
        else printf("NONE\n");
    }
    printf("END OF OUTPUT\n");
    
    return 0;
}

计算三角形的外心
外心: 三角形三条边的中垂线交点与三角形三个顶点间的距离等长,可作为三角形外接圆的圆心,该交点亦称谓三角形的外心。

利用欧拉公式计算多面体

平面图与欧拉公式

  • 平面图: 若一个图能画在平面上使它的边除了在顶点处外互不相交,则称该图为平面图,或称该图能嵌入平面的。

  • 欧拉公式: 若连通平面图G有n个顶点,e条边和f个面,则n-e+f=2,亦被称为欧拉公式。

  • 欧拉多面体公式: 若一个多面体有n个顶点,e条边和f个面,则n−e+f=2,亦被称为欧拉多面体公式。

求n,e,f中的一个,求出另外两个就好了。
例题: UVA 10213

#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <algorithm>
#define ll long long 
using namespace std;

ll cas,n;
void print(__int128 x){
	if(x > 9)
		print(x/10);
	putchar(x%10 + '0');
}
int main(){
	scanf("%lld",&cas);
	while(cas--){
		scanf("%lld",&n);
		__int128 f;
		f = ((__int128)n*n*n*n - (__int128)6*n*n*n + (__int128)23*n * n -(__int128)18*n)/(__int128)24 + (__int128)1;
		print(f);
		puts("");
	}
	return 0;
}

描线算法的实验


扫描线算法计算矩形面积的并

两个实验:

沿垂直方向计算矩形的并面积;
沿水平方向计算矩形的并面积;

算法过程:

  • 离散:将平面分割成若干条;
  • 扫描:采用扫描法对条进行扫描,并用线段树存储条;
  • 线段树:通过线段树的插入和删除操作,计算n个矩形的面积并。
    (还没有学会)

计算半平面交的实验


半平面交的联机算法

设n个半平面的交 H 1 ∩ H 2 ∩ … ∩ H n {H_1∩H_2∩…∩H_n} H1H2Hn组成凸多边形A。初始时A为整个平面;然后,依次用Hi的边界线 a i x + b i y + c i = 0 {a_{i}x +b_{i}y + c_{i} = 0} aix+biy+ci=0切割A,保留A中使不等式 a i x + b i y + c i &gt; = 0 {a_{i}x +b_{i}y + c_{i} &gt;= 0} aix+biy+ci>=0成立的部分,1≤i≤n。最后,得到的A就是 H 1 ∩ H 2 ∩ … ∩ H n {H_1∩H_2∩…∩H_n} H1H2Hn

利用极角计算半平面交的算法

**极角:**对于平面X-Y中的一点,以及连接该点与原点直接的一条直线,极角 θ {\theta} θ是由x轴逆时针和这条直线的夹角。
如下图:
在这里插入图片描述

凸包计算和旋转卡壳算法的实验


凸包计算

凸包:

Graham算法:

旋转卡壳

切线:
对踵点对:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值