模拟退火

模拟退火(Simulate Anneal)是一种通用概率演算法,在大的搜索空间内寻找最优解,若新的状态优于当前状态,则将新的状态作为最优解,否则以一定概率接受新的状态。

模拟退火有三个因数,初始温度\(T_0\),降温系数\(d\),终止温度\(T_k\),通常不直接取当前解为最优解,而是维护退火过程中遇到的所有解的最优值。

inline void SimulateAnneal(){
    double t=3000;/*初始温度*/
    while(t>1e-15){/*尚未达到终止温度,可以用时间代替*/
        double nx=ansx+(rand()*2-RAND_MAX)*t,ny=ansy+(rand()*2-RAND_MAX)*t/*下一个点的坐标*/,re=calc(nx,ny)/*下一个点的答案*/,delta=re-ans;/*两点答案的差值*/
        if(delta<0){/*下一个点比当前点更优*/
            ansx=nx;
            ansy=ny;
            ans=re;
        }
        else if(exp(-delta/t)*RAND_MAX>rand()){/*以多项式概率接受下一个点的坐标*/
            ansx=nx;
            ansy=ny;
        }
        t*=0.97;/*逐步降温*/
    }
}

\(n\)个点的带权类费马点。

首先选定一个答案点并计算,之后挑选根据温度跳出一段距离,比较那个点的答案并决定是更新答案还是以一定概率接受坐标。

inline double calc(double a,double b){
    double re=0;
    for(int i=1;i<=n;i++){
        double dx=x[i]-a,dy=y[i]-b;
        re+=sqrt(dx*dx+dy*dy)*w[i];
    }
    return re;
}
inline void SimulateAnneal(){
    double t=3000;
    while(t>1e-15){
        double nx=ansx+(rand()*2-RAND_MAX)*t,ny=ansy+(rand()*2-RAND_MAX)*t,re=calc(nx,ny),delta=re-ans;
        if(delta<0){
            ansx=nx;
            ansy=ny;
            ans=re;
        }
        else if(exp(-delta/t)*RAND_MAX>rand()){
            ansx=nx;
            ansy=ny;
        }
        t*=0.97;
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d%d%d",x+i,y+i,w+i),ansx+=x[i],ansy+=y[i];
    ansx/=n;
    ansy/=n;
    ans=calc(ansx,ansy);
    for(int i=1;i<=3;i++)SimulateAnneal();
    printf("%.3lf %.3lf\n",ansx,ansy);
    return 0;
}

每头牛有三个朋友,求一个排列使得每对朋友之间的距离和最小。

首先列出一个排列并计算答案,每次降温时选出两头牛并交换它们的位置,注意要去重。

inline int calc(){
    int re=0;
    for(int i=1;i<=n;i++)for(int j=1;j<=3;j++)re+=abs(pos[i]-pos[f[i][j]]);
    return re;
}
inline void SimulateAnneal(){
    double t=3000;
    while(t>1e-15){
        int x=rand()%n+1,y=rand()%n+1;
        while(x==y)x=rand()%n+1,y=rand()%n+1;
        swap(pos[x],pos[y]);
        int re=calc(),delta=re-ans;
        if(delta<0)ans=re;
        else if(exp(-delta/t)*RAND_MAX<rand())swap(pos[x],pos[y]);
        t*=0.97;
    }
}

\(n\)个数分为\(m\)组,计算每组的和,求这\(m\)个数的最小方差。

对于给定序列,为了让方差最小,需要在加入数时加入当前和最小的一组,降温时随机交换两个数。

inline double calc(){
    double re=0,tmp[N];
    memset(tmp,0,sizeof(tmp));
    for(int i=1;i<=n;i++){
        int p=1;
        for(int j=1;j<=m;j++)if(tmp[j]<tmp[p])p=j;
        tmp[p]+=be[i];
    }
    for(int i=1;i<=m;i++)re+=(tmp[i]-ave)*(tmp[i]-ave);
    return sqrt(1.0*re/m);
}
inline void SimulateAnneal(){
    double t=10000;
    for(int i=1;i<=n;i++)be[i]=bl[i];
    while(t>1e-15){
        int x=rand()%n+1,y=rand()%n+1;
        while(x==y)x=rand()%n+1,y=rand()%n+1;
        swap(be[x],be[y]);
        double re=calc(),delta=re-ans;
        if(delta<0){
            ans=re;
            for(int i=1;i<=n;i++)bl[i]=be[i];
        }
        else if(exp(-delta/t)<rand())swap(be[x],be[y]);
        t*=0.97;
    }
}
    for(int i=1;i<=n;i++)scanf("%d",a+i),be[i]=bl[i]=a[i],ave+=a[i];
    ave/=m;

给出\(n+1\)个点,求一个\(n\)维球的球心坐标。

初始答案点为所有点坐标和的平均值,之后计算每个维度和其他点的距离并求平均值,根据平均值计算差值,结合温度进行爬山。

inline double dist(const node&a,const node&b){
    double re=0;
    for(int i=1;i<=n;i++)re+=(a.a[i]-b.a[i])*(a.a[i]-b.a[i]);
    return sqrt(re);
}
inline void SimulateAnneal(){
    double t=10000;
    while(t>0.0001){
        double ave=0;
        for(int i=1;i<=n+1;i++)dis[i]=dist(ans,p[i]),ave+=dis[i];
        ave/=n+1;
        for(int i=1;i<=n;i++)delta.a[i]=0;
        for(int i=1;i<=n+1;i++)for(int j=1;j<=n;j++)delta.a[j]+=(dis[i]-ave)*(p[i].a[j]-ans.a[j])/ave;
        for(int i=1;i<=n;i++)ans.a[i]+=delta.a[i]*t;
        t*=0.99999;
    }
}
    for(int i=1;i<=n+1;i++)for(int j=1;j<=n;j++)scanf("%lf",(p+i)->a+j),ans.a[j]+=p[i].a[j]/(n+1);

\(n\)个城市\(m\)个堡垒,可以使\(k\)个城市成为堡垒,\(dis(x)\)表示距离城市\(x\)到最近的堡垒的距离,求\(max(dis(i))\)的最小值。

每次降温时暴力求最短路,随机已选的一个点和可选但是未选的点进行交换,注意当可以全部成为堡垒时要特判。

inline void spfa(){
    queue<int>q;
    memset(dis,0x3f,sizeof(dis));
    memset(in,0,sizeof(in));
    for(int i=1;i<=m;i++)dis[pos[i]]=0,q.push(pos[i]);
    for(int i=1;i<=k;i++)dis[can[i]]=0,q.push(can[i]);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        in[x]=false;
        for(int i=h[x];i;i=e[i].next){
            int y=e[i].to;
            if(dis[y]>dis[x]+e[i].w){
                dis[y]=dis[x]+e[i].w;
                if(in[y]==false){
                    in[y]=true;
                    q.push(y);
                }
            }
        }
    }
}
inline int calc(){
    spfa();
    int re=0;
    for(int i=1;i<=n;i++)re=max(re,dis[i]);
    return re;
}
inline void SimulateAnneal(){
    double t=3000;
    while(t>1e-15){
        int x=rand()%k+1,y=rand()%(can[0]-k)+1;
        swap(can[x],can[k+y]);
        int re=calc(),delta=re-ans;
        if(delta<0)ans=re;
        else if(exp(-delta/t)*RAND_MAX<=rand())swap(can[x],can[k+y]);
        t*=0.997;
    }
}
    }
    for(int i=1;i<=m;i++)cin>>pos[i],in[++pos[i]]=true;
    for(int i=1;i<=n;i++)if(in[i]==false)can[++can[0]]=i;

将一个序列分成两组且数量插值不超过\(1\),求两组和的差值的最小值。

排序后进行分组,随机交换两组中的数,注意特判只有一组的情况。

inline int calc(){
    int re=0;
    for(int i=1;i<=b[0];i++)re+=b[i];
    for(int i=1;i<=c[0];i++)re-=c[i];
    return abs(re);
}
inline void SimulateAnneal(){
    double t=3000;
    while(t>1e-15){
        int x=rand()%b[0]+1,y=rand()%c[0]+1;
        swap(b[x],c[y]);
        int re=calc(),delta=re-ans;
        if(delta<0)ans=re;
        else if(exp(-delta/t)*RAND_MAX<=rand())swap(b[x],c[y]);
        t*=0.999;
    }
}
        sort(a+1,a+1+n);
        for(int i=1;i<=n;i++){
            if(i&1)b[++b[0]]=a[i];
            else c[++c[0]]=a[i];
        }
        ans=calc();
        if(b[0]==0||c[0]==0){
            cout<<ans<<'\n';
            continue;
        }
posted @ 2022-11-14 18:07  半步蒟蒻  阅读(351)  评论(0)    收藏  举报