Bzoj 3680: 吊打XXX && luogu P1337 [JSOI2004]平衡点 / 吊打XXX (模拟退火)
Bzoj : https://www.lydsy.com/JudgeOnline/problem.php?id=3680
luogu : https://www.luogu.org/problemnew/show/P1337
上午闲来无事,没有比赛可以打....正好洛谷日报更新了模拟退火这篇文章,然后学习了一下,这道题应该是模拟退火的入门经典问题了吧.
看出来这是一道物理题
接下来引自attack的博客.
我们所需要求的点,一定是总能量最小的点,这里的总能量,就是每个点的重力势能之和,如果让一个点的重力势能减小,那么拉它的绳子就应该尽量的长,那么在桌面上的绳子就应该尽量的短
因此我们需要求得一个点,使得(sum_{i=1}^nd[i]∗w[i])最小 ((d[i])表示该到平衡点的距离,(w[i])表示该点的重量).
发现这个中间点极值分布的极其不均.
我们就用模拟退火解决.
话说求极值的东西都可以用退火搞一下呢.(神奇)
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstdlib>
const int maxN = 10000 + 7;
const double eps = 1e-16;
using namespace std;
struct Point {
double x,y,w;
}Map[maxN];
int n;
double Rand(double T) {return T * ( ( rand() << 1 ) - RAND_MAX);}
double calc(double x,double y) {
double ans = 0;
for(int i = 1;i <= n;++ i)
ans += sqrt( ( x - Map[i].x ) * ( x - Map[i].x ) + ( y - Map[i].y ) * ( y - Map[i].y ) ) * Map[i].w ;
return ans;
}
int main() {
srand(20030327);
scanf("%d",&n);
double Begin_x,Begin_y,Best_ans,Best_x,Best_y;
for(int i = 1;i <= n;++ i) {
scanf("%lf%lf%lf",&Map[i].x,&Map[i].y,&Map[i].w);
Begin_x += Map[i].x;Begin_y += Map[i].y;
}
Begin_x /= n;Begin_y /= n;
Best_ans = calc(Begin_x,Begin_y);
Best_x = Begin_x,Best_y = Begin_y;
double Delate = 0.98;
int Time = 10;
while(Time --) {
double Now = calc(Begin_x,Begin_y),Now_x = Begin_x,Now_y = Begin_y;
for(double T = 1000000;T > eps;T *= Delate) {
double tmp_x = Now_x + Rand(T),tmp_y = Now_y + Rand(T);
double tmp_ans = calc(tmp_x,tmp_y);
if(tmp_ans < Best_ans) {Best_ans = tmp_ans;Best_x = tmp_x;Best_y = tmp_y;}
if(tmp_ans < Now || exp( (tmp_ans - Now) / T ) * RAND_MAX < rand()) {
Now_x = tmp_x,Now_y = tmp_y;
Now = tmp_ans;
}
}
}
printf("%.3lf %.3lf", Best_x, Best_y);
return 0;
}