计算几何基础入门(1)

                                       平面最接近点对&&二维凸包&&最小覆盖圆  模板

  首先要知道两个基础知识   叉积与基础运算符重载   二维叉积可以用来判断点与点的位置关系与面积  (三维叉积可算平面的法向量)

  这些模板都是二维的  我想想看能不能化为三维的题 空间最小点对 三维凸包 最小覆盖球

  例题   P4894、 P3744、P2785

   

        平面最接近点对模板讲解

 最容易想到的方法是n2暴力法  但一分析数据范围就会发现肯定会t  于是我们有了下面一个思路  先将点按x排序再运用归并把数对最终分为左右然后在合并   那么最短距离就变为了左右最短距离与跨越分界线的最短距离这三种情况      难点就只在跨越分界线的了    关键点就是能够点的距离小于d的点的数量是很少的  我们的任务就是只找出并计算这些点  关键在代码内有说明

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000001 ;
const int INF=2<<20;
int n,temp[maxn];
struct Point{double x,y;}S[maxn];
bool cmp(const Point &a,const Point&b ){if(a.x==b.x)return a.y<b.y; else return a.x<b.x;}
bool cmps(const int &a,const int &b){return S[a].y<S[b].y ;}
double min(double a,double b){return a<b?a:b;}
double dist(int i,int j) ///求距离
{
    double x=(S[i].x-S[j].x)*(S[i].x-S[j].x);
    double y=(S[i].y-S[j].y)*(S[i].y-S[j].y);
    return sqrt(x+y);
}

double merge(int left,int right) ///只判断x小于d&&y也小于d的点对
{
    double d=INF;
    if(left==right)
        return d ;
    if(left+1==right)
        return dist(left,right);
    int mid=left+right>>1;
    double d1=merge(left,mid) ;///
    double d2=merge(mid+1,right) ;
    d=min(d1,d2);
    int i,j,k=0;
    for(i=left;i<=right;i++)
        if(fabs(S[mid].x-S[i].x)<=d)///选出 点与分界线x距离小于d的点
            temp[k++]=i;
    sort(temp,temp+k,cmps);///按y排序
    for(i=0;i<k;i++)
        for(j=i+1;j<k&&S[temp[j]].y-S[temp[i]].y<d;j++)///选出点与点y距离小于d的点
        {
            double d3=dist(temp[i],temp[j]);///最终可证明每次只要判断不超过6个点的距离  一个d*2d矩阵范围内的点
            if(d>d3)d=d3;
        }
    return d;
}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)scanf("%lf%lf",&S[i].x,&S[i].y);
    sort(S,S+n,cmp);///先按x排序 好划分分界线
    printf("%.4lf\n",merge(0,n-1));
    return 0;
}

 二维凸包模板讲解

 经分析只要我们选最外层的点组合成图形就是最短围栏  这就是二维凸包   这时我们采用的方法是Graham法  一个经典算法   关键就在于如何判断一个点是不是凸包的构成点   建议画一个凸包并看代码分析   代码注释写了关键

#include<bits/stdc++.h>
using namespace std;
int n;
struct ben
{
    double x,y;
}p[10005],s[10005];
double check(ben a1,ben a2,ben b1,ben b2)///检查叉积是否大于0,如果是a就逆时针转到b
{
    return (a2.x-a1.x)*(b2.y-b1.y)-(b2.x-b1.x)*(a2.y-a1.y);
}
double d(ben p1,ben p2)///两点间距离
{
    return sqrt((p2.y-p1.y)*(p2.y-p1.y)+(p2.x-p1.x)*(p2.x-p1.x));
}
bool cmp(ben p1,ben p2)///排序函数,这个函数别写错了,要不然功亏一篑
{
    double tmp=check(p[1],p1,p[1],p2);
    if(tmp>0)
        return 1;
    if(tmp==0&&d(p[0],p1)<d(p[0],p2))
        return 1;
    return 0;
}
int main()
{

    scanf("%d",&n);
    double mid;
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf",&p[i].x,&p[i].y);
        if(i!=1&&p[i].y<p[1].y)///点的交换 p[1]为最低点
        {
            mid=p[1].y;p[1].y=p[i].y;p[i].y=mid;
            mid=p[1].x;p[1].x=p[i].x;p[i].x=mid;
        }
    }
    sort(p+2,p+1+n,cmp);///系统快排  利用叉积排序
    s[1]=p[1];///最低点一定在凸包里
    int cnt=1;
    for(int i=2;i<=n;i++)///s数组储存凸包端点
    {                                                       ///怎么判断一个点会不会被踢走
                                                            /// 主要看点形成的边是向右转了还是向左转了
        while(cnt>1&&check(s[cnt-1],s[cnt],s[cnt],p[i])<=0) ///判断前面的会不会被踢走,如果被踢走那么出栈
            cnt--;
        cnt++;
        s[cnt]=p[i];
    }
    s[cnt+1]=p[1];///最后一个点回到凸包起点
    double ans=0;
    for(int i=1;i<=cnt;i++)
        ans+=d(s[i],s[i+1]);///最小长度为边长和
    printf("%.2lf\n",ans);
    return 0;
}

最小覆盖圆模板讲解

 这题的关键是  不断向现在形成的圆加入新的点  如果这点不在此圆内时   那么符合最小圆概念的话 这个新点它一定在符合的圆的边界上   运用这个思路我们可以求出三个点    来求这个圆    由中垂线求圆心  中点直接/2  方向就是右转90°   用向量来表示直线   

#include<bits/stdc++.h>
#define ll long long
using namespace std;

struct vec
{
    double x, y;
    vec (const double& x0 = 0, const double& y0 = 0) : x(x0), y(y0) {}///重载+-*/
    vec operator + (const vec& t) const {return vec(x+t.x, y+t.y);}
    vec operator - (const vec& t) const {return vec(x-t.x, y-t.y);}
    vec operator * (const double& t) const {return vec(x*t, y*t);}
    vec operator / (const double& t) const {return vec(x/t, y/t);}
    const double len2 () const {return x*x + y*y;}
    const double len () const {return sqrt(len2());}///向量模长
    vec norm() const {return *this/len();}///标准向量
    vec rotate_90_c () {return vec(y, -x);}///向右转90度
};

double dot(const vec& a, const vec& b) {return a.x*b.x + a.y*b.y;}
double crs(const vec& a, const vec& b) {return a.x*b.y - a.y*b.x;}///叉积

vec lin_lin_int(const vec& p0, const vec& v0, const vec& p1, const vec& v1)
{
    double t = crs(p1-p0, v1) / crs(v0, v1);///运用叉积求交点
    return p0 + v0 * t;
}

vec circle(const vec& a, const vec& b, const vec& c)///中垂线求圆心  直线用向量表示
{
    return lin_lin_int((a+b)/2, (b-a).rotate_90_c(), (a+c)/2, (c-a).rotate_90_c());
}

int n;
vec pot[100005];

int main()
{
    scanf("%d", &n);
    for(int i=1; i<=n; i++) scanf("%lf%lf", &pot[i].x, &pot[i].y);
    random_shuffle(pot+1, pot+n+1);///随机化  不随机化会有大问题
    vec o;///o 为圆心  r2为半径
    double r2 = 0;
    for(int i=1; i<=n; i++)
    {
        if((pot[i]-o).len2() > r2)///出现一个点不在现在圆的范围内   立刻形成一个新圆
        {                         ///经分析 可知这个点一定在圆边界上
            o = pot[i], r2 = 0;
            for(int j=1; j<i; j++)
            {
                if((pot[j]-o).len2() > r2)///类似  这时已确定两个在边界上的点了
                {
                    o = (pot[i]+pot[j])/2, r2 = (pot[j]-o).len2();
                    for(int k=1; k<j; k++)
                    {
                        if((pot[k]-o).len2() > r2)///三点确定圆
                        {
                            o = circle(pot[i], pot[j], pot[k]), r2 = (pot[k]-o).len2();
                        }
                    }
                }
            }
        }
    }
    printf("%.10lf\n%.10lf %.10lf\n", sqrt(r2), o.x, o.y);
    return 0;
}

看到最后面的彩蛋  : 轻音乐   Speak Softly,Love   

猜你喜欢

转载自www.cnblogs.com/yurenwuyu/p/12546389.html