计算几何学是研究几何问题的算法,在现代工程与数学,诸如计算机图形学、计算机辅助设计、机器人学都要应用计算几何学。因此计算几何学是算法体系中的一个重要组成部分。
点线面运算的实验
计算点积和叉积
点积:定义 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∣∣∣∣=x1∗y2−x2∗y1=−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 ⃗ < 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 ⃗ = 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,...,pn−1为使多边形的边首尾相接,另设 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=2∣∑n=1n−2pi ^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} H1∩H2∩…∩Hn组成凸多边形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 > = 0 {a_{i}x +b_{i}y + c_{i} >= 0} aix+biy+ci>=0成立的部分,1≤i≤n。最后,得到的A就是 H 1 ∩ H 2 ∩ … ∩ H n {H_1∩H_2∩…∩H_n} H1∩H2∩…∩Hn。
利用极角计算半平面交的算法
**极角:**对于平面X-Y中的一点,以及连接该点与原点直接的一条直线,极角
θ
{\theta}
θ是由x轴逆时针和这条直线的夹角。
如下图:
凸包计算和旋转卡壳算法的实验
凸包计算
凸包:
Graham算法:
旋转卡壳
切线:
对踵点对: