题解 P1337 【[JSOI2004]平衡点 / 吊打XXX】
SuperJvRuo · · 题解
前面的几篇题解似乎都没有重点说模拟退火的算法。
一、模拟退火步骤
选定一个初始状态(比如选定所有点坐标的平均数),选定一个初始温度T。
当温度大于一个边界值时:
{
随机变化坐标,变化幅度为 T 。
计算新解与当前解的差 DE。
如果新解比当前解优(DE > 0),就用新解替换当前解。
否则以 exp(DE / T) 的概率用新解替换当前解。
温度乘上一个小于1的系数,即降温。
}
这样,随着温度不断降低,变化幅度也越来越小,接受一个更劣的解的概率也越来越小。
二、模拟退火注意事项
-
温度T的初始值设置问题。 初始温度高,则搜索到全局最优解的可能性大,但因此要花费大量的计算时间;反之,则可节约计算时间,但全局搜索性能可能受到影响。
-
退火速度问题。 模拟退火算法的全局搜索性能也与退火速度密切相关。同一温度下的“充分”搜索(退火)是相当必要的,但这需要计算时间。
-
温度管理问题 降温系数应为正的略小于1.00的常数。
关于这道题,一切自然变化进行的方向都是使能量降低,因为能量较低的状态比较稳定。
因为物重一定,绳子越短,重物越低,势能越小,势能又与物重成正比,所以,只要使得
#include<cstdio>
#include<cctype>
#include<cmath>
#include<cstdlib>
int Read()
{
int x=0,y=1;
char c=getchar();
while(!isdigit(c))
{
if(c=='-')
{
y=-1;
}
c=getchar();
}
while(isdigit(c))
{
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return x*y;
}
struct Node
{
int x,y,weight;
}node[10005];
int n;
double potential_energy(double nowx,double nowy)
{
double sum=0;
for(int i=1;i<=n;i++)
{
double delx=nowx-node[i].x;
double dely=nowy-node[i].y;
sum+=(sqrt(delx*delx+dely*dely))*node[i].weight;
//物重一定,绳子越短,重物越低,势能越小
//势能又与物重成正比
}
return sum;//在(nowx,nowy)处的总势能
}
double xans,yans;//最终答案
double ans=1e18+7,t;//势能与温度
const double delta=0.993;//降温系数
void simulate_anneal()
{
double xx=xans;//钦定一个初始位置
double yy=yans;
t=1926;//t是温度
while(t>1e-14)
{
double xtemp=xans+(rand()*2-RAND_MAX)*t;
double ytemp=yans+(rand()*2-RAND_MAX)*t;
//随机一个新的坐标,变化幅度为t
//这里要注意rand()和rand()*2-RAND_MAX的区别
//rand()的范围是0~RAND_MAX-1
//rand()*2-RAND_MAX的范围是-RAND_MAX到RAND_MAX-1
double new_ans=potential_energy(xtemp,ytemp);//计算当前解的势能
double DE=new_ans-ans;
if(DE<0)//如果是一个更优解
{
xx=xtemp;
yy=ytemp;//就接受
xans=xx;
yans=yy;
ans=new_ans;
}
else if(exp(-DE/t)*RAND_MAX>rand())//能否接受这个差
{
//更新坐标
xx=xtemp;
yy=ytemp;
}
t*=delta;//降温
}
}
void SA()//洗把脸就AC了
{
simulate_anneal();
simulate_anneal();
simulate_anneal();
}
int main()
{
n=Read();
for(int i=1;i<=n;i++)
{
node[i].x=Read();
node[i].y=Read();
node[i].weight=Read();
}
SA();
printf("%.3lf %.3lf",xans,yans);
return 0;
}