原題鏈接
關於模擬退火的詳細介紹,可以peng-ym關於模擬退火的介紹。
題目描述
- 如圖:有n個重物,每個重物系在一條足夠長的繩子上。每條繩子自上而下穿過桌面上的洞,然后系在一起。圖中X處就是公共的繩結。假設繩子是完全彈性的(不會造成能量損失),桌子足夠高(因而重物不會垂到地上),且忽略所有的摩擦。
- 問繩結X最終平衡於何處。
- 注意:桌面上的洞都比繩結X小得多,所以即使某個重物特別重,繩結X也不可能穿過桌面上的洞掉下來,最多是卡在某個洞口處。
輸入輸出格式
- 輸入格式:
文件的第一行為一個正整數n(1≤n≤1000),表示重物和洞的數目。接下來的n行,每行是3個整數:Xi.Yi.Wi,分別表示第i個洞的坐標以及第 i個重物的重量。(-10000≤x,y≤10000, 0<w≤1000 ) - 輸出格式:
你的程序必須輸出兩個浮點數(保留小數點后三位),分別表示處於最終平衡狀態時繩結X的橫坐標和縱坐標。兩個數以一個空格隔開。
解題思路
- 這題怕不是OI中為數不多的與物理有關系的題。
(233) - 題目詢問的是繩結最終平衡於何處?
- 根據物理的知識,當系統處於平衡狀態時,系統的總能量最小。
- 又此時系統的總能量是等於各個物體的重力勢能,在質量一定時,即要求物體離地最近,離桌子最遠。
- 那么,也就是繩子在桌子上的距離盡量的小。即:\(\sum_{i=1}^{n}m_i*dist_{i,x}\)最小。
- 模擬退火要解決的問題就是找到這一個點的位置。
- 模擬退火最主要的參數有幾個:\(T_0\)初始溫度,\(t\)每一次下降的溫度,\(ans\)目前為止最優的答案,\(now\)新的狀態,\(delta\)當前答案與最優答案的差值。
- 在擴展狀態時有一個小方法:\((rand()*2-RANDMAX)*T\)。這樣的原理是\((rand()*2-RANDMAX)\)的范圍是從負數到正數的,這樣子在擴展坐標的時候就可以多方向擴展,不會只在一個方向上更新。
- (PS:還有一個很重要的問題,玄學調參。這種問題最好在可以看到評測結果的OJ上交,不然你不會知道是自己打錯了,還是參數沒調好。。。。。。)
直接上代碼:
#include<bits/stdc++.h>
#define N 2000
using namespace std;
template<typename T>inline void read(T &x)
{
x=0;
static int p;p=1;
static char c;c=getchar();
while(!isdigit(c)){if(c=='-')p=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+(c-48);c=getchar();}
x*=p;
}
struct node
{
double x,y,w;
}e[N];
int n;
double ansx,ansy;
const double eps=1e-15;
double f(double x,double y)
{
double tot=0;
for(int i=1;i<=n;i++)
{
double delx=x-e[i].x;
double dely=y-e[i].y;
tot+=sqrt(delx*delx+dely*dely)*e[i].w;
}
return tot;
}
void mnth()
{
double T=200;
while(T>eps)
{
double nowx=ansx+(rand()*2-RAND_MAX)*T;
double nowy=ansy+(rand()*2-RAND_MAX)*T;
double delta=f(nowx,nowy)-f(ansx,ansy);
if(delta<0)ansx=nowx,ansy=nowy;
else if(exp(-delta/T)*RAND_MAX>rand())ansx=nowx,ansy=nowy;
T*=0.998;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("mnth.in","r",stdin);
freopen("mnth.out","w",stdout);
#endif
srand((int)time(NULL));
read(n);
for(int i=1;i<=n;i++)
{
scanf("%lf%lf%lf",&e[i].x,&e[i].y,&e[i].w);
ansx+=e[i].x;ansy+=e[i].y;
}
ansx/=(double)n;ansy/=(double)n;
mnth();
printf("%.3lf %.3lf\n",ansx,ansy);
return 0;
}