模拟退火

(多扶老奶奶过马路,可以增长\(RP\)偶)

模拟退火主要是求最优解的一种方法

模拟退火算法来源于固体退火原理,是一种基于概率的算法,将固体加温至充分高,再让其徐徐冷却,加温时,固体内部粒子随温升变为无序状,内能增大,而徐徐冷却时粒子渐趋有序,在每个温度都达到平衡态,最后在常温时达到基态,内能减为最小。
----某度某科

这是模拟退火的模拟图

模拟退火就是在降温过程中随机新的答案,并判断答案是否更优,如果更优,就接受新的结果,否则就以一定的概率接受新的答案

温度越高,解的变化量越大,随着温度的逐渐降低,解的变化量也渐渐变小,并越发集中在最优解附近

模拟退火在洛谷日报上有详细的解释,如果不会模拟退火可以先去康康

就是这

我们首先看这个题,我们直接模拟退火就好了

但要注意一点\(exp(- del / temp) * RAND_MAX < rand()\)

这里是小于(因为这是求最大值,如果求最小值就用大于就好了)

然后就是注意这个题求最大值就\(OK\)

#include<bits/stdc++.h>
using namespace std;
#define eps 1e-10//看脸的东西
#define xita 0.9972//同上
const int N = 15, M = 1005;
double n, m, R, sumx, sumy, a[M], b[M], ans, ax, ay;
struct node{
    double x, y, r;
}s[N];
double dis(double x, double y, double xx, double yy){
    return sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy));
}//求距离
double query(double x, double y){//计算答案
    double res = 0, d = R;
    for(int i = 1; i <= n; i ++){
        d = min(d, dis(x, y, s[i].x, s[i].y) - s[i].r);
    }//求炸弹最大的爆炸半径
    for(int i = 1; i <= m; i ++){
        if(dis(x, y, a[i], b[i]) <= d) res ++;
    }//统计能炸到的僵尸
    return res;
}
void SA(){//模拟退火
    double ansx = ax, ansy = ay;
    double temp = 2605;//初始温度,一般为2000~3000左右(自己定)
    while(temp > eps){
        double xx = ansx + ((rand() << 1) - RAND_MAX) * temp;
        double yy = ansy + ((rand() << 1) - RAND_MAX) * temp;
        //rand()一组新的坐标
        double nowans = query(xx, yy);//当前的答案
        double del = nowans - ans;//求差值
        if(del > 0){//如果更优,就更新答案
            ax = xx; ay = yy;
            ansx = xx; ansy = yy;
            ans = nowans;
        }else{
            if(exp(- del / temp) * RAND_MAX < rand()){//否则就以一个概率接受这个答案,(这个概率特别小)
                ansx = xx;
                ansy = yy;
            }
        }
        temp *= xita;//降温
    }
}
void cz(){
    ax = sumx / m; ay = sumy / m;//开始最好用平均值跑
    for(int i = 1; i <= 6; i ++){//多跑几遍模拟退火
        SA();
    }
}
int main(){
    srand(1e9 + 7);//随机种子
    scanf("%lf %lf %lf", &n ,&m, &R);   
    for(int i = 1; i <= n; i ++){
        scanf("%lf %lf %lf", &s[i].x, &s[i].y, &s[i].r);
    }
    for(int i = 1; i <= m; i ++){
        scanf("%lf %lf", &a[i], &b[i]);        
        sumx += a[i]; sumy += b[i];//求总和
    }
    cz();
    printf("%.0lf\n", ans);//输出,答案要求为整数,我定义的double,所以直接保留0位小数就好了
    return 0;
}

希望此篇题解能够对您起到帮助(不要像我一样,交了好几十遍)