很可惜,充滿Mo力的Mo擬退火並不是正解。不過這是一道最適合開始入手Mo擬退火的好題。
對模擬退火還不是很清楚的可以看一下
這道題還真和能量有點關系。達到平衡穩態的時候,物體的總能量應該是最小的。而總的能量來源於每個物體的重力勢能之和。要想讓某個物體勢能減小,那就讓拉着它的繩子在桌面下方的長度盡可能的長,也就是桌面上的要盡可能短。由此看來,某個物體的勢能與桌面上的繩子的長度、物體重量都成正比。
於是,為了找到平衡點,我們要找一個點使得\(\sum_{i=1}^n d_i*w_i\)最小(\(d_i\)為\(i\)點到該點的距離)。
函數已經求出來了,接下來就是正常的模擬退火過程。注意一些細節就好了。
首先,初始解可以設成\(({\sum_{i=1}^n x_i\over n},{\sum_{i=1}^n y_i\over n})\),可以更接近正解
解變動值最好隨機兩個值,\(\Delta x\)和\(\Delta y\)。隨機變動距離和角度可能常數有點大。
然后是寫法問題。蒟蒻又學習了一招,知道有一個常數叫RAND_MAX,用於生成\([0,1)\)的隨機值還是很方便的,在不同機子下可移植性也很強。(難怪Windows上rand出來的都是短整形數)
另外,因為退火的時候有可能本來找到了最優解,但后來接受了較劣解並逐步降溫穩定了,那么也就是說本來找到的最優解到最后卻給弄丟了。
為了避免這種情況,蒟蒻參考YL的做法,設一個全局最優解best,它只接受比自己更優的解。
而且這樣還有一個好處,就是進行多次退火的過程中,也能保證best是單調遞減的,無論如何也不會變劣。
最后就是調參數的問題了。
發現自己寫了個假的模擬退火以后,蒟蒻馬上一改,卻發現參數又需要重新調。。。。。。(YL據老也背了鍋)
拼命的二分,二分。。。。。。參數大得都要TLE了還是不能保證正確率?!
結果小號提交記錄第二次刷屏了(第一次還是打表過洛谷愚人節T3。。。。。。)
后來突然猛地一想:\(n,x,y,w\)都不小,乘來乘去不會掉精度了吧?
於是改用long double繼續,情況好多了
update:
可能發現在哪里咕咕了,應該不是\(n,x,y,w\)的問題。
隨機變動距離寫的是T*(rand()*2-RAND_MAX)
,也就意味着生成了一個\([-T*RAND\_MAX,T*RAND\_MAX)\)的數。
這沒有錯,因為RD的值域確實與T成正比,但不對勁的是它太大了,難道是因為這里爆double精度?
可是看到其它巨佬用double沒出鍋誒。。。
其實還有一個優化就是,退火結束后從best處向小范圍內rand一些點,rand個千把次,找最優的一個點作為答案。
這樣比多次退火效率高了不少,就更容易彌補之前沒找到最優解的遺憾了。
聽XZY大佬的講課學的,stO XZY Orz
代碼蒟蒻就懶得重寫了。。。。。。(連個理由都懶得找)
下面的參數可供參考,因為D算是很小的了,所以跑得快多了,只要28ms
#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstdlib>
#define RG register
#define R RG long double
#define RD T*(rand()*2-RAND_MAX)//生成一個[-T*RAND_MAX,T*RAND_MAX)的隨機變動距離
//以前寫的是T*((double)rand()/RAND_MAX*2-1),總是WA(可能是先除后乘掉精度了),然后就參考了YL的寫法
const int N=1009;
const long double D=0.97,EPS=1e-14;//更新后二分出了保證極高正確率和較高效率的參數
double x[N],y[N],w[N];
int n;
inline long double calc(R x0,R y0){
R res=0,dx,dy;
for(RG int i=1;i<=n;++i){//函數求值
dx=x[i]-x0;dy=y[i]-y0;
res+=sqrt(dx*dx+dy*dy)*w[i];
}
return res;
}
int main(){
R T,x0,y0,x1,y1,res,ans,best,bx=0,by=0;
RG int i,times=1;//一次的正確率很高的,不放心也可以調大
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%lf%lf%lf",&x[i],&y[i],&w[i]);
bx+=x[i];by+=y[i];
}//初始橫縱坐標均選擇平均值
best=ans=calc(bx/=n,by/=n);
srand(time(NULL));
while(times--/*clock()<CLOCKS_PER_SEC*0.9*/){//比賽的時候試試注釋里寫的,卡時還是靠譜些
ans=best;x0=bx;y0=by;
for(T=100000;T>EPS;T*=D){
x1=x0+RD;y1=y0+RD;
res=calc(x1,y1);
if(best>res)
best=res,bx=x1,by=y1;//更新最優答案
if(ans>res||exp((ans-res)/T)>(long double)rand()/RAND_MAX)
ans=res,x0=x1,y0=y1;//接受新解
}
};
printf("%.3Lf %.3Lf\n",bx,by);
return 0;
}