[2018.3.26集训]yja-伪模拟退火

Posted zltttt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[2018.3.26集训]yja-伪模拟退火相关的知识,希望对你有一定的参考价值。

题目大意

在平面上找 n 个点, 要求这 n 个点离原点的距离分别为 $r_1,r_2, \cdots r_rn$. 最大化这 n 个点构成的凸包面积, 凸包上的点的顺序任意。
注意点不一定全都要在凸包上。

$n \leq 8,r_i \leq 1000$

题解

正解是拉格朗日乘数法。
然而作为一只蒟蒻,当然是不会这种巧妙的操作的。

那就乱搞吧!
考虑使用模拟退火算法。

初始给每个节点随机一个相对于原点的角度,并计算它们构成的凸包面积。
设定初始温度为$\pi$(角度为弧度制),并进行退火。
每次从所有点中随机选择一个点,对其角度加上或减去温度乘上一个在$(0,1)$范围内随机的实数,计算此时的答案。
若更优,则应用这次修改,否则不作出修改。

随机$10$次左右即可稳定得到最优解~
说这是伪模拟退火的原因是,模拟退火事实上还会根据温度一定概率接受一次错误的修改,而实测这样的效果并不是特别优。

代码:

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

typedef double db;
typedef pair<db,db> pr;
const int N=19;
const db eps=1e-8;
const db pi=acos(-1);
const db mint=1e-7;
#define x first
#define y second

int n,stk[N],rr[N];
db r[N];
db ang[N],ans=0.0;
pr p[N];

inline bool cmp(pr a,pr b)
{
    if(abs(a.x-b.x)<eps)
        return a.y<b.y;
    return a.x<b.x;
}

pr operator - (pr a,pr b)
{
    return pr(a.x-b.x,a.y-b.y);
}

inline bool cmp2(pr a,pr b)
{
    return atan2(a.y-p[1].y,a.x-p[1].x)<atan2(b.y-p[1].y,b.x-p[1].x);
}

inline db randf()
{
    return (db)rand()/(db)RAND_MAX;
}

inline db cross(pr a,pr b)
{
    return a.x*b.y-a.y*b.x;
}

inline db hull()
{
    for(int i=1;i<=n;i++)
        p[i]=pr(cos(ang[i])*r[i],sin(ang[i])*r[i]);
    sort(p+1,p+n+1,cmp);
    sort(p+2,p+n+1,cmp2);

    int top;
    stk[top=1]=1;
    for(int i=2;i<=n;i++)
    {
        while(top>=2 && cross(p[i]-p[stk[top-1]],p[stk[top]]-p[stk[top-1]])>eps)
            top--;
        stk[++top]=i;
    }


    db ret=0;
    for(int i=1;i<top;i++)
        ret+=cross(p[stk[i]],p[stk[i+1]]);
    ret+=cross(p[stk[top]],p[stk[1]]);
    return fabs(ret/2.0);
}

inline void work()
{
    for(int i=1;i<=n;i++)
        ang[i]=randf()*pi*2.0;

    db t=pi,cans=hull();
    while(t>mint)
    {
        //for(int i=1;i<=1;i++)
        {
            int choose=rand()%n+1;
            db inc=t*(randf()-0.5);
            ang[choose]+=inc;
            db tans=hull();
            if(tans>cans)cans=tans;
            else ang[choose]-=inc;
        }
        t*=0.994;
    }

    ans=max(ans,cans);
}

inline int spj()
{
    for(int i=1;i<=n;i++)
        ang[i]=((db)(i-1)/(db)n)*pi*2.0;
    printf("%.6f\n",hull());
    return 0;
}

int main()
{
    freopen("yja.in","r",stdin);
    freopen("yja.out","w",stdout);

    srand(time(0));
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&rr[i]);
        r[i]=(db)rr[i];
    }

    for(int i=2;i<=n;i++)
        if(r[i]!=r[i-1])
            goto hell;
    return spj();

    hell:;
    int T=200;
    while(T--)
        work();
    printf("%.8f\n",ans);
    return 0;
}

以上是关于[2018.3.26集训]yja-伪模拟退火的主要内容,如果未能解决你的问题,请参考以下文章

数学建模暑期集训23:模拟退火算法

模拟退火入门——求解TSP和洛谷P2210

模拟退火 (poj 2420, poj 2069)

Matlab:数模06-模拟退火模型

TSP 遗传算法

模拟退火算法通俗讲解