计算几何进阶-随机算法

该专题主要是学会模拟退火这个玄学算法,我会在另一篇博客详细介绍模拟退火。
随机算法嘛,难免会错个几次,交的时候就是一把梭,wa了就wa了。
A - Run Away
给出n个点,求距所有点的最小距离最大的那个点的坐标。
没啥好说的,直接上模版。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<math.h>
#include<time.h>
using namespace std;
const int maxn=1005;
const double inf=1e10;
const double eps=1e-6;
const double pi=acos(-1.0);
double px[maxn],py[maxn];
double d[maxn];
double cx[maxn],cy[maxn];
double dist(double x1,double y1,double x2,double y2)
{
    double x=x1-x2;
    double y=y1-y2;
    return sqrt(x*x+y*y);
}
int main()
{
    //freopen("input.txt","r",stdin);
    int T;
    scanf("%d",&T);
    srand(time(NULL));
    while(T--)
    {
        int R,C,M;
        scanf("%d %d %d",&R,&C,&M);
        double x,y;
        for(int i=1; i<=M; i++)
        {
            scanf("%lf %lf",&x,&y);
            px[i]=x,py[i]=y;
        }
        for(int i=1; i<=30; i++)
        {
            cx[i]=(rand()%1000+1)*1.0/1000*R;
            cy[i]=(rand()%1000+1)*1.0/1000*C;
            double thed=inf;
            for(int j=1; j<=M; j++)
                thed=min(thed,dist(cx[i],cy[i],px[j],py[j]));
            d[i]=thed;
        }
        double hot=max(R,C)/(sqrt(1.0*M));
        while(hot>eps)
        {
            for(int i=1; i<=30; i++)
            {
                for(int k=1; k<=30; k++)
                {
                    x=cx[i],y=cy[i];
                    double tmp=rand()%10000*1.0/10000*10*pi;
                    double dx=x+hot*cos(tmp);
                    double dy=y+hot*sin(tmp);
                    if(dx>R||dx<0||dy>C||dy<0)
                        continue;
                    double thed=inf;
                    for(int j=1; j<=M; j++)
                        thed=min(thed,dist(dx,dy,px[j],py[j]));
                    if(d[i]<thed)
                    {
                        d[i]=thed;
                        cx[i]=dx;
                        cy[i]=dy;
                    }
                }
            }
            hot*=0.8;
        }
        double ansd=-1;
        int idx;
        for(int i=1; i<=30; i++)
            if(ansd<d[i])
            {
                ansd=d[i];
                idx=i;
            }
        printf("The safest point is (%.1lf, %.1lf).\n",cx[idx],cy[idx]);
    }
    return 0;
}

B - A Star not a Tree?
给出n个点,求一个与这n个点的距离和最小的点。
和上面一样。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
#include<time.h>
using namespace std;
const int maxn=105;
const double eps=1e-6;
const double inf=1e10;
const double pi=acos(-1.0);
int px[maxn],py[maxn];
double cx[maxn],cy[maxn];
double d[maxn];
double dis(double x1,double y1,double x2,double y2)
{
    double x=x1-x2;
    double y=y1-y2;
    return sqrt(x*x+y*y);
}
int main()
{
    //freopen("input.txt","r",stdin);
    srand(time(NULL));
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d %d",&px[i],&py[i]);
    for(int i=1;i<=30;i++)
    {
        cx[i]=rand()%10001*1.0/10000*10000;
        cy[i]=rand()%10001*1.0/10000*10000;
        double thed=0;
        for(int j=1;j<=n;j++)
            thed+=dis(cx[i],cy[i],px[j],py[j]);
        d[i]=thed;
        //cout<<d[i]<<endl;
    }
    double hot=10000/sqrt(1.0*n);
    while(hot>eps)
    {
        for(int i=1;i<=30;i++)
        {
            double x=cx[i],y=cy[i];
            for(int j=1;j<=30;j++)
            {
                double tmp=rand()%10001*1.0/10000*10*pi;
                double dx=hot*cos(tmp);
                double dy=hot*sin(tmp);
                double tmpx=x+dx;
                double tmpy=y+dy;
                if(tmpx<0||tmpx>10000||tmpy<0||tmpy>10000)continue;
                double tmpd=0;
                for(int k=1;k<=n;k++)
                {
                    tmpd+=dis(tmpx,tmpy,px[k],py[k]);
                    //cout<<tmpd<<endl;
                }
                if(d[i]>tmpd)
                {
                    cx[i]=tmpx;
                    cy[i]=tmpy;
                    d[i]=tmpd;
                }
            }
        }
        hot*=0.8;
    }
    double ans=inf;
    for(int i=1;i<=30;i++)
    {
        ans=min(ans,d[i]);
    }
    printf("%.0f\n",ans);
}

C - Empire Strikes Back
给出一个圆形区域和n个点,求与这n个点的最小的距离最大的那个点。
以下是摘录特判
这里写图片描述

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
#include<time.h>
using namespace std;
const int maxn=305;
const double inf=1e18;
const double eps=1e-6;
const double pi=acos(-1.0);
int sgn(double x)
{
    if(fabs(x)<eps)return 0;
    if(x>0)return 1;
    return -1;
}
struct Point
{
    double x,y;
    double len;
    Point(double _x,double _y):x(_x),y(_y){len=sqrt(x*x+y*y);}
    Point(){}
    void read()
    {
        scanf("%lf %lf",&x,&y);
        len=sqrt(x*x+y*y);
    }
}p[maxn];
int N,R;
double dis(Point a,Point b)
{
    double x=a.x-b.x;
    double y=a.y-b.y;
    return sqrt(x*x+y*y);
}
double F(Point a)
{
    double res=inf;
    for(int i=1;i<=N;i++)
        res=min(res,dis(a,p[i]));
    return res;
}

double Rand(double num)
{
    return (rand()%10001+0.0)/10000*num;
}

bool OutCircle(double x,double y)
{
    return sgn(x*x+y*y-R*R)>0;
}

double solve()
{
    int times=15;
    double res=0;
    while(times--)
    {
        double tmpmax;
        double hot=R;
        double x=Rand(R),y=Rand(R);
        while(OutCircle(x,y))
            x=Rand(R),y=Rand(R);
        tmpmax=F(Point(x,y));
        while(hot>eps)
        {
            for(int t=1;t<=15;t++)
            {
                double tmp=Rand(1)*10*pi;
                double dx=hot*cos(tmp);
                double dy=hot*sin(tmp);
                double cx=x+dx,cy=y+dy;
                if(OutCircle(cx,cy))continue;
                double tmpdis=F(Point(cx,cy));
                if(tmpdis>tmpmax)
                {
                    tmpmax=tmpdis;
                    x=cx,y=cy;
                }
            }
            hot*=0.9;
        }
        res=max(res,tmpmax);
    }
    return res;
}

int main()
{
    //freopen("input.txt","r",stdin);
    srand(time(NULL));
    scanf("%d %d",&N,&R);
    for(int i=1;i<=N;i++)
        p[i].read();
    double ans=0;
    for(int i=1;i<=N;i++)
    {
        if(sgn(p[i].len)==0)continue;
        double tmpx=1.0*p[i].x/p[i].len*R;
        double tmpy=1.0*p[i].y/p[i].len*R;
        Point tmp=Point(tmpx,tmpy);
        ans=max(ans,F(tmp));
        tmp=Point(-tmpx,-tmpy);
        ans=max(ans,F(tmp));
    }
    //cout<<ans<<endl;
    for(int i=1;i<=N;i++)
        for(int j=i+1;j<=N;j++)
    {
        if(sgn(p[i].x-p[j].x)!=0||sgn(p[i].y-p[j].y)!=0)
        {
            if(sgn(p[i].y-p[j].y)==0)
            {
                double tmpx=(p[i].x+p[j].x)/2;
                double tmpy=sqrt(R*R-tmpx*tmpx);
                Point tmp=Point(tmpx,tmpy);
                ans=max(ans,F(tmp));
                tmp=Point(tmpx,-tmpy);
                ans=max(ans,F(tmp));

            }
            else
            {
                double k=-(p[j].x-p[i].x+0.0)/(p[j].y-p[i].y);
                double x=(p[i].x+p[j].x+0.0)/2;
                double y=(p[i].y+p[j].y+0.0)/2;
                double b=y-k*x;
                double delta=4*k*k*b*b-4*(k*k+1)*(b*b-R*R);
                double tmpx=(-2*k*b+sqrt(delta))/(2*(k*k+1));
                double tmpy=k*tmpx+b;
                Point tmp=Point(tmpx,tmpy);
                ans=max(ans,F(tmp));
                tmpx=(-2*k*b-sqrt(delta))/(2*(k*k+1));
                tmpy=k*tmpx+b;
                tmp=Point(tmpx,tmpy);
                ans=max(ans,F(tmp));
                //cout<<ans<<endl;
            }
        }
    }
    ans=max(ans,solve());
    printf("%.5f\n",ans);

}

D - Super Star
找到一个点,使得以这个点为球心,作一个能包围所有点的球,并且半径最小。
实际上还是求与这n个点的最远距离最小的坐标,然而不同的是这次是三维坐标,用上面的方法进行随机总是会wa,所以得增加一点贪心的思想,尽量与距当前点最远的那个点的方向走。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<math.h>
#include<time.h>
using namespace std;
const int maxn=35;
const double eps=1e-9;
const double pi=acos(-1.0);
const double inf=1e10;
int sgn(double x)
{
    if(fabs(x)<eps)return 0;
    if(x>0)return 1;
    return -1;
}
struct Point3
{
    double x,y,z;
    Point3(double _x,double _y,double _z):x(_x),y(_y),z(_z) {}
    Point3() {}
    void read()
    {
        scanf("%lf %lf %lf",&x,&y,&z);
    }
};

double Rand(int num)
{
    return rand()%10001*1.0/10000*num;
}

double dis(Point3 a,Point3 b)
{
    double x=a.x-b.x;
    double y=a.y-b.y;
    double z=a.z-b.z;
    return sqrt(x*x+y*y+z*z);
}

Point3 p[maxn];
int n;
void solve()
{
    int times=10;
    double ans=inf;
    while(times--)
    {
        double hot=150;
        double x=Rand(100),y=Rand(100),z=Rand(100);
        double tmpmin=0;
        for(int i=1;i<=n;i++)
            tmpmin=max(tmpmin,dis(Point3(x,y,z),p[i]));
        while(hot>eps)
        {
            double maxdis=-1;
            int idx;
            for(int i=1;i<=n;i++)
            {
                double thedis=dis(Point3(x,y,z),p[i]);
                if(sgn(maxdis-thedis)<0)
                    maxdis=thedis,idx=i;
            }
            x=x+(p[idx].x-x)/maxdis*hot;
            y=y+(p[idx].y-y)/maxdis*hot;
            z=z+(p[idx].z-z)/maxdis*hot;
            hot*=0.99;
        }
        double tmpmin2=0;
        for(int i=1;i<=n;i++)
            tmpmin2=max(tmpmin2,dis(Point3(x,y,z),p[i]));
        tmpmin=min(tmpmin,tmpmin2);
        ans=min(ans,tmpmin);
    }
    printf("%.5lf\n",ans+eps);
}

int main()
{
    srand(time(NULL));
    //freopen("input.txt","r",stdin);
    while(~scanf("%d",&n)&&n)
    {
        for(int i=1; i<=n; i++)
            p[i].read();
        solve();
    }

}

E - Texas Trip
求能包围n个点的最小正方形。
按照题目要求求正方形的最小面积,如果正方形的一边平行于x轴,那么其面积就是x方向上的距离和y方向的距离中最大的那个平方。如果不是平行于x轴,那我们就得计算那个方向和与那个方向垂直的方向上的最大值。此时我们就会想到坐标旋转。
既然是最小值,那么面积公式显然有一个单调减的过程,并且旋转的性质能保证面积公式是一个周期函数,所以显然得到它是有一个单峰的函数。此时我们会想到三分。
注意,三分的精度要求很高。。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<math.h>
using namespace std;
const int maxn=35;
const double eps=1e-9;
const double pi=acos(-1.0);
int sgn(double x)
{
    if(fabs(x)<eps)return 0;
    if(x>0)return 1;
    return -1;
}
struct Point
{
    double x,y;
    Point(double sx,double sy):x(sx),y(sy){}
    Point(){}
    void read()
    {
        scanf("%lf %lf",&x,&y);
    }
}p[maxn];
int n;
double f(double num)
{
    double maxx=-1000,maxy=-1000,minx=1000,miny=1000;
    for(int i=1;i<=n;i++)
    {
        double x=p[i].x*cos(num)-p[i].y*sin(num);
        double y=p[i].x*sin(num)+p[i].y*cos(num);
        if(sgn(maxx-x)<0)maxx=x;
        if(sgn(maxy-y)<0)maxy=y;
        if(sgn(minx-x)>0)minx=x;
        if(sgn(miny-y)>0)miny=y;
    }
    return max(maxx-minx,maxy-miny);
}

int main()
{
    //freopen("input.txt","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--)
    {

        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            p[i].read();
        double L=0,R=pi/2;
        while(sgn(L-R)<0)
        {
            double mid1=(L+R)/2;
            double mid2=(mid1+R)/2;
            if(sgn(f(mid1)-f(mid2))<0)
                R=mid2;
            else L=mid1;
        }
        double ans=f(L);
        printf("%.2f\n",ans*ans);
    }
    return 0;
}

F - Point of view in Flatland
题意:给出三个圆,让你求出一个点,要求该点与圆作出两条切线之间的角度相等。
一开始没看懂题意,于是就去翻题解了,结果无意看到判断函数使用角度的方差来计算。。
中间随机的方式我用之前的做法写了,不管怎么改都会wa。。看了题解才学会另一种方式。只能说模拟退火真玄学。。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<time.h>
#include<math.h>
using namespace std;
const double eps=1e-7;
const double inf=1e10;
const double pi=acos(-1.0);
int sgn(double x)
{
    if(fabs(x)<eps)return 0;
    if(x>0)return 1;
    return -1;
}
struct Circle
{
    double x,y,r;
    Circle(double _x,double _y,double _r):x(_x),y(_y),r(_r){}
    Circle(double _x,double _y):x(_x),y(_y){}
    Circle(){}
    void read()
    {
        scanf("%lf %lf %lf",&x,&y,&r);
    }
};
Circle C[5];
typedef Circle Point;

double dis(Point a,Point b)
{
    double x=a.x-b.x;
    double y=a.y-b.y;
    return sqrt(x*x+y*y);
}

double F(Point a)
{
    double tmp[4];
    for(int i=1;i<=3;i++)
        tmp[i]=C[i].r/dis(a,C[i]);
    double sum=tmp[1]+tmp[2]+tmp[3];
    sum/=3;
    double res=0;
    for(int i=1;i<=3;i++)
        res+=sqrt(10*(sum-tmp[i])*(sum-tmp[i]));
    //cout<<res/3<<endl;
    return res;

}

double Rand(int num)
{
    return rand()%10001*1.0/10000*num;
}
Point e1,e2;
double Rang2(double a,double b)
{
    return a+Rand(1)*(b-a);
}
void solve()
{
    int times=15;
    double ans=inf;
    double ansx,ansy;
    while(times--)
    {
        double x=Rang2(e1.x,e2.x),y=Rang2(e1.y,e2.y);
        double value=F(Point(x,y));
        double hot=max(e2.x-e1.x,e2.y-e1.y);
        while(hot>eps)
        {
            for(int k=1;k<=15;k++)
            {
                //double tmp=Rand(1)*10*pi;
                //double dx=hot*cos(tmp),dy=hot*sin(tmp);
                double cx=Rang2(max(e1.x,x-hot),min(e2.x,x+hot));
                double cy=Rang2(max(e1.y,y-hot),min(e2.y,y+hot));
                double itF=F(Point(cx,cy));
                if(sgn(itF-value)<0)
                {
                    value=itF;
                    x=cx;
                    y=cy;
                }
            }
            hot*=0.8;
        }
        if(sgn(ans-value)>0)
        {
            ans=value;
            ansx=x;
            ansy=y;
        }
    }
    if(sgn(ans))
        puts("No solution");
    else printf("%.2f %.2f\n",ansx,ansy);
}

bool read()
{
    e1.x=e1.y=inf;
    e2.x=e2.y=-inf;
    bool flag=0;
    for(int i=1;i<=3;i++)
    {
        C[i].read();
        if(C[i].x!=0||C[i].y!=0||C[i].r!=0)flag=1;
        if(sgn(e1.x-C[i].x)>0)e1.x=C[i].x;
        if(sgn(e1.y-C[i].y)>0)e1.y=C[i].y;
        if(sgn(e2.x-C[i].x)<0)e2.x=C[i].x;
        if(sgn(e2.y-C[i].y)<0)e2.y=C[i].y;
    }
    return flag;
}

int main()
{
    srand(time(NULL));
    //freopen("input.txt","r",stdin);
    while(read())
        solve();
}

G - Geometrical dreams
这题很显然随机找到第一个点,然后按照题意要求算出其余的点的坐标,并以最后一个点再推出第一个点,用两个第一个点的坐标距离来当作判断函数。
然而我还是不管怎么改还是wa。。结果发现是精度问题,我写的时候看到discuss上说输出用%.01输出。。结果我改成%.0f就过了。。所以discuss上也不一定对的。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
#include<math.h>
#include<time.h>
using namespace std;
const double inf=1e10;
const int maxn=55;
const double eps=1e-7;
const double pi=acos(-1.0);
int sgn(double x)
{
    if(fabs(x)<eps)return 0;
    if(x>0)return 1;
    return -1;
}
struct Point
{
    double x,y;
    Point(double _x,double _y):x(_x),y(_y){}
    Point(){}
    void read()
    {
        scanf("%lf %lf",&x,&y);
    }
    void out()
    {
        cout<<x<<' '<<y<<endl;
    }
    Point operator-(const Point &b)const
    {
        return Point(x-b.x,y-b.y);
    }
    Point operator+(const Point &b)const
    {
        return Point(x+b.x,y+b.y);
    }
    double operator*(const Point &b)const
    {
        return x*b.x+y*b.y;
    }
    double operator&(const Point &b)const
    {
        return x*b.y-y*b.x;
    }
}p[maxn];
double angle[maxn];
Point e1,e2;
double Rand(int num)
{
    return rand()%10001*1.0/10000*num;
}

double Range(double a,double b)
{
    return a+Rand(1)*(b-a);
}
Point ans[maxn];

Point Rotate(Point a,double ang)
{
    double x=a.x*cos(ang)-a.y*sin(ang);
    double y=a.x*sin(ang)+a.y*cos(ang);
    return Point(x,y);
}
int n;
void solve2(Point a)
{
    ans[1]=a;
    for(int i=1;i<=n;i++)
    {
        Point tmp=ans[i]-p[i];
        tmp=Rotate(tmp,angle[i]);
        tmp=tmp+p[i];
        ans[i+1]=tmp;
    }
}

double dis(Point a,Point b)
{
    double x=a.x-b.x;
    double y=a.y-b.y;
    return sqrt(x*x+y*y);
}

double F(Point a)
{
    solve2(a);
    return dis(a,ans[n+1]);
}

void solve()
{
    int times=15;
    double res=inf;
    double ansx,ansy;
    while(times--)
    {
        double hot=max(e2.x-e1.x,e2.y-e1.y);
        double x=Range(e1.x,e2.x);
        double y=Range(e1.y,e2.y);
        double MinF=F(Point(x,y));
        while(hot>eps)
        {
            for(int k=1;k<=15;k++)
            {
                double tmpx=Range(max(e1.x,x-hot),min(e2.x,x+hot));
                double tmpy=Range(max(e1.y,y-hot),min(e2.y,y+hot));
                double itF=F(Point(tmpx,tmpy));
                if(sgn(itF-MinF)<0)
                {
                    MinF=itF;
                    x=tmpx;
                    y=tmpy;
                }
            }
            hot*=0.9;
        }
        if(sgn(res-MinF)>0)
        {
            res=MinF;
            ansx=x;
            ansy=y;
        }
    }
    solve2(Point(ansx,ansy));
    for(int i=1;i<=n;i++)
        printf("%.0f %.0f\n",ans[i].x,ans[i].y);

    //tmp.out();
    //cout<<cos(angle[1])<<endl;
    //cout<<sin(angle[1])<<endl;
}

int main()
{
    //freopen("input.txt","r",stdin);
    srand(time(NULL));
    e1.x=e1.y=inf;
    e2.x=e2.y=-inf;

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        p[i].read();
        //p[i].out();
        if(sgn(e1.x-p[i].x)>0)e1.x=p[i].x;
        if(sgn(e1.y-p[i].y)>0)e1.y=p[i].y;
        if(sgn(e2.x-p[i].x)<0)e2.x=p[i].x;
        if(sgn(e2.y-p[i].y)<0)e2.y=p[i].y;
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%lf",&angle[i]);
        angle[i]=angle[i]/180*pi;
    }
    solve();
}

实习搞得我好tm烦啊。。
垃圾湖工计算机学院大家不要报考。。

猜你喜欢

转载自blog.csdn.net/qq_34921856/article/details/80831762