专题3之模拟退火(蜜汁玄学)

这个算法真心玄学,网上还一堆假代码,假博客QAQ,还有并软用的样例。。。
还是总结总结吧
这次有四道题用了模拟退火Ellipsoid(也可以三分套三分)、Run Away、Groundhog Build Home、A Chocolate Manufacturer's Problem

常用

计算单个点到已知所有点的最值等问题

注意点

1、所有类型用double !!!!!。。。有三题分别WA在函数形参用int,中途变量用int,定义数组用int。。这种错误,在自己编译的时候编译器帮你强制转化了,放到OJ上就GG
2、根据题目要求的精度,设计T,delta和随机次数,太小了容易精度不够,太大了就容易TLE
3、GetVal的计算一定要仔细,笔者基本都是这里出错
4、HDU的。。。注意是多case!多case!多case!
5、不要漏要求输出的任何字符!!

接下来就发发题解

HDU 5017Ellipsoid

题意大致是求从原点到椭圆面的最短距离
解法大概有三分套三分,模拟退火。。。正所谓学了假高数,不会拉格朗日定理的我只能退火了

/*坑点大概就是一开始看了一份假题解
   后来WA就是没考虑方程的负解
   还有就是参数类型错误,一定要用double
*/
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define LL long long
#define CLR(x) memset(x, 0, sizeof(x))
const double INF = 1e10;
const double eps = 1e-9;

int dx[8] = {-1, -1, -1, 0, 0, 1, 1, 1}; //因为是三维坐标所有有八个方向走
int dy[8] = {-1, 0, 1, -1, 1, -1, 0, 1};

double a, b, c, d, e, f;

double dist(double x, double y, double z)
{
    return sqrt(x*x + y*y + z*z);
}

double GetVal(double x, double y) 
{ //啦啦解方程
    double A = c;
    double B = e*x + d*y;
    double C = a*x*x + b*y*y + f*x*y-1;
    double D  = B*B - 4*A*C; 
    if (D < 0) return INF;

    double det = sqrt(D);

    double res1 = (-B + det) / (2*A);
    double res2 = (-B - det) / (2*A);

    return dist(x,y,res1) < dist(x,y,res2) ? res1 : res2;
}

double Search()
{
    double T = 1; //初始温度
    double delta = 0.99;

    double x = 0, y = 0, z = sqrt(1/c);

    while (T > eps)
    {
        for (int i = 0; i < 8; i++)
        {
            double r = x + dx[i]*T;
            double c = y + dy[i]*T;
            double k = GetVal(r, c);
            if (k > INF) continue;
            if (dist(r,c,k) < dist(x,y,z))
            {
                x = r; 
                y = c;
                z = k;
            }
        }
        T *= delta;
    }
    return dist(x, y, z);

}

int main()
{
    while(scanf("%lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &e, &f) != EOF)
    {
        printf("%.7lf\n", Search());
    }    
}

HDU 1109Run Away

大致题意,给n个点,在平面内选一个点A,使A到所有点的距离和最小,并求最小距离。

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define LL long long
#define CLR(x) memset(x, 0, sizeof(x))
const double INF = 1e8;
const double eps = 1e-3;

int dx[5] = {-1, 0, 1, 0}; //平面内
int dy[5] = {0, 1, 0, -1};

double X, Y;
int M;
double U[1005], V[1005];

struct node //数组和结构体都可用
{
    double x, y;
    double val;
} p[55];

double dist(double x, double y, double a, double b)
{
    return (x-a)*(x-a) + (y-b)*(y-b);
}

double GetSum(node h)
{
    double d = INF;
    for (int i = 0; i < M; i++)
        d = min(d, dist(h.x, h.y, U[i], V[i])); 
    return d;
}


double Search()
{
    double T = X+Y;
    double delta = 0.9;
    for (int i = 0; i <= 50; i++)
    {
        p[i].x = (rand()%1000+1) * 1.0/1000.00 * X; //队内dalao说可以暴力,不用随机
        p[i].y = (rand()%1000+1) * 1.0/1000.00 * Y;
        p[i].val = GetSum(p[i]);
    }
    node tmp;
    while (T > eps)
    {
        for (int i = 0; i <= 50; i++)
        {
            for (int j = 0; j <= 50; j++)
            {
                double r = (double)(rand()%1000+1)*1.0/1000.00*T;
                double c = sqrt(T*T - r*r);

                if (rand()&1) r *= -1;
                if (rand()&1) c *= -1;

                tmp.x = p[i].x + r;
                tmp.y = p[i].y + c;

                if (tmp.x<0 || tmp.x>X || tmp.y<0 || tmp.y>Y) continue;

                if (GetSum(tmp) > p[i].val)
                {
                    p[i] = tmp;
                    p[i].val = GetSum(tmp);
                }
            }
        }
        T *= delta;
    }
    int cur = 1;
    for (int i = 0; i <= 50; i++)
        if (p[i].val > p[cur].val)
            cur = i;
    printf("The safest point is (%.1lf, %.1lf).\n", p[cur].x, p[cur].y);
   //不要漏了句号!!!!血一般的教训
}

int main()
{
    srand(unsigned(time(0)));
    int t;
    scanf("%d", &t);
    while(t--)
    {
        CLR(U);
        CLR(V);
        scanf("%lf%lf%d", &X, &Y, &M);
        for (int i = 0; i < M; i++)
            scanf("%lf%lf", &U[i], &V[i]);
        Search();
    }
}

HDU 3932Groundhog Build Home

大致题意是一直土拨鼠要找一个离他所有朋友家都最近的点,并输出到最近朋友家的最大距离。
其实就是平面上的最小圆覆盖问题,这种单点到多点的用退火很很合适,然后就是,为了避免局域最大值印象答案,在这题和下一题,笔者都用分块随机数

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define LL long long
#define CLR(x) memset(x, 0, sizeof(x))
#define MAX 100000000*1LL
#define PI acos(-1)
const double eps = 1e-2;
const double INF = 1e8;

double X, Y;
int N;
double px[1005], py[1005];

struct node
{
    double x, y, val;
} p[1005];

double dist(double x, double y, double a, double b)
{
    return sqrt((x-a)*(x-a) + (y-b)*(y-b));
}

double GetMax(node t)
{
    double d = 0;
    for (int i = 0; i < N; i++)
        d = max(d, dist(t.x, t.y, px[i], py[i])); //注意,这里是最大值
    return d;
}

void Search()
{
    double delta = 0.6; //!!!太接近1会TLE
    double T = max(X, Y);
    node tmp;

    while(T > eps)
    {
        for(int i = 0; i < N; i++)
        {
            for(int j = 0; j < 20; j++) //这里也是,过大会TLE
            {
                double d = (rand()%1000+1)/1000.0*2*PI;
                tmp.x = p[i].x + cos(d)*T;
                tmp.y = p[i].y + sin(d)*T;

                if (tmp.x<=0 || tmp.x>=X || tmp.y<=0 || tmp.y>=Y) continue;

                if (GetMax(tmp) < p[i].val)
                {
                    p[i] = tmp;
                    p[i].val = GetMax(tmp);
                }
            }
        }
        T *= delta;
    }

    node res;
    res.val = INF;;
    for (int i = 0; i < N; i++) // 求出全局最优解
        if (p[i].val < res.val) res = p[i];

    printf("(%.1lf,%.1lf).\n", res.x, res.y);
    printf("%.1lf\n", res.val);
}

int main()
{
    srand(unsigned(time(0)));
    while(scanf("%lf%lf%d", &X, &Y, &N) != EOF)
    {
        CLR(px);
        CLR(py);
        for (int i = 0; i < N; i++)
            scanf("%lf%lf", &px[i], &py[i]);

        for (int i = 0; i < N; i++) /*随机取N个点。。。笔者原本是最直接改上一题的代码。。。
                                      结果出事情了。。。事实告诉我们随机数要谨慎*/
        {
            p[i].x = (rand()%1000+1)/1000.0*X;
            p[i].y = (rand()%1000+1)/1000.0*Y;
            p[i].val = GetMax(p[i]);
        }
        Search();
    }
}

HDU 3644A Chocolate Manufacturer's Problem

大致题意求一个多边形内最大的内接圆

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define LL long long
#define CLR(x) memset(x, 0, sizeof(x))
#define MAX 100000000*1LL
#define PI 3.14159265358
//#define zero(x) (((x)>0?(x):(-x)) 0 ? 1 : -1;
}

double dist(node a, node b)
{
    return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}

double cross(node k1, node k2, node k0)
{
    return (k1.x-k0.x)*(k2.y-k0.y) - (k2.x-k0.x)*(k1.y-k0.y);
}

double Disptosege(node& l1, node& l2, node& k ) //求解点到线段的距离!!
{
    double a, b, c;
    a = dist(l1, k);
    b = dist(l2, k);
    c = dist(l1, l2);

    if(zero(a*a+c*c-b*b) < 0) return a;
    else if(zero(b*b+c*c-a*a) < 0) return b;
    else return fabs(cross(k, l1, l2)/c);
}

bool inside_convex(node a)  //判断点是不是在多边形内
{
    int cnt = 0;
    double tmp;

    for(int i = 0; i < n; i++)
    {
        if( (p[i].y<=a.y && p[i+1].y>a.y) || (p[i+1].y<=a.y && p[i].y>a.y))
        {
            if (zero(p[i].y-p[i+1].y))
                tmp = p[i+1].x - (p[i+1].x-p[i].x)*(p[i+1].y-a.y)/(p[i+1].y-p[i].y);
            else
            {
                if(!zero(p[i].y-a.y)) cnt++;
                tmp = -INF;
            }
            if(zero(tmp - a.x) >= 0) cnt++;
        }
    }
    return cnt&1;
}


void cal(node& a)
{
    double t;
    a.R = INF;
    for( int i = 0; i < n; ++i )
    {
        t = Disptosege(p[i], p[i+1], a);
        a.R = min(a.R, t);
    }
}

node mid[105];

void Search()
{
    bool flag = false;
    double delta = 0.90;
    double T = sqrt(MidX*MidX + MidY*MidY)/2.0;
    int k = 0;
    while(!flag && T > eps)
    {
        for (int i = 0; i < n; i++)
        {
            k++;
            if (flag) break;
            for (int j = 0; j < 5; j++)
            {
                if (flag) break;
                node tmp;
                double angle = rand();
                tmp.x = mid[i].x + cos(angle)*T;
                tmp.y = mid[i].y + sin(angle)*T;

                if (inside_convex(tmp))
                {
                    cal(tmp);
                    if (tmp.R > mid[i].R)
                    {
                        mid[i] = tmp;
                        //mid[i].R = GetVal(tmp);
                        if (zero(tmp.R - R) >= 0) flag = true;
                    }
                }
            }
        }
        T *= delta;
    }
    //printf("%d\n", k);
    if (flag) printf("Yes\n");
    else printf("No\n");
}

int main()
{
    //srand(unsigned(time(0)));
    while(scanf("%d", &n)==1 && n)
    {
        double MaxX=0, MaxY = 0;
        double MinX=INF, MinY=INF;
        for (int i = 0; i < n; i++)
        {
            scanf("%lf%lf", &p[i].x, &p[i].y);
            MaxX = max(MaxX, p[i].x);
            MaxY = max(MaxY, p[i].y);
            MinX = min(MinX, p[i].x);
            MinY = min(MinY, p[i].y);
        }
        MidX = MaxX - MinX;
        MidY = MaxY - MinY;

        scanf("%lf", &R);
        p[n] = p[0];
        for (int i = 0; i < n; i++)
        {
            mid[i].x = (p[i].x + p[i+1].x) / 2; //取中点
                                               //队内一位dalao取右上点也过了,但是左下点一定WA
            mid[i].y = (p[i].y + p[i+1].y) / 2;
            mid[i].R = 0;
        }
        Search();
    }
}

总结
数据只能判断大体没问题,概率,温度,如何随机去点还是要根据情况定,如何逼近最优解

你可能感兴趣的:(专题3之模拟退火(蜜汁玄学))