Bzoj 3680: 吊打XXX (模拟退火)

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;
}
posted @ 2018-09-29 14:10  Rlif  阅读(205)  评论(0编辑  收藏  举报