P5544 【[JSOI2016]炸弹攻击1】
模拟退火
(多扶老奶奶过马路,可以增长\(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;
}
希望此篇题解能够对您起到帮助(不要像我一样,交了好几十遍)