模擬退火入門——POJ 2420
阿新 • • 發佈:2018-11-12
題目連結:
POJ 2420
題目大意:
給出平面上N(<=100)個點,你需要找到一個這樣的點,使得這個點到N個點的距離之和儘可能小。輸出這個最小的距離和(四捨五入到最近的整數)
解題思路:
如果下午不是馬原課無聊,學習了一下模擬退火的思想,大概看到這題也沒太多想法,不過是真的神奇啊,先放上我覺得講得很簡單的模擬退火演算法的一個講解
大白話解析模擬退火演算法
看完之後,總結就是,每次把下一個狀態和當前狀態對目標答案進行比較,如果比當前更優或者其exp(dE/T) 在(0,1)之間,則進行退火(即T乘以某個不大於1的正數),然後不斷更新下一狀態,直到T小於某個T_min(一般比較小,1e-3這樣吧,具體看題目,或者經驗也是很重要感覺2333,包括上面對T退火的那個數也有講究,多練吧!)
最後就有很可能得到最優解了!!!
回到本題,初始狀態的話,當然是所有點的平均位置的那個點,目標答案是和其他點的距離和,這裡我們每次隨機生成一個角度,然後用當前點座標和T得到下一個狀態的座標。計算各到其他點的距離和,記錄最小值,然後不斷退火,就可以很大概率得到最優解
AC程式碼:
#include <iostream> #include <algorithm> #include <cstdio> #include <cmath> #include <cstring> #define INF 1e17 #define EPS 1e-3 #define PI acos(-1) #define FIRE(x) (x *= 0.99) using namespace std; struct Point{ double x,y; Point(){} Point(double _x, double _y):x(_x),y(_y){} void operator +=(const Point t){ x += t.x; y += t.y; } }; Point p[150],now; int n; double ans; double getDist(double x,double y){ double ret = 0; for(int i=0; i<n; ++i){ ret += sqrt((p[i].x-x)*(p[i].x-x)*1.0 + (p[i].y-y)*(p[i].y-y)*1.0); } if(ret < ans) ans = ret; return ret; } double Rand(){ return (rand()%1000+1)/1000.0; } void solve(){ double T = 100000.0,alpha,sub; while(T > EPS){ alpha = 2.0*PI*Rand(); Point tmp(now.x + T*cos(alpha), now.y + T*sin(alpha)); sub = getDist(now.x,now.y) - getDist(tmp.x,tmp.y); if(sub >=0 || exp(sub/T) >= Rand()) now = tmp; FIRE(T); } } int main(){ srand(100233); scanf("%d",&n); for(int i=0; i<n; ++i){ scanf("%lf%lf",&p[i].x,&p[i].y); now += p[i]; } ans = 0x3f3f3f3f; now.x /=n; now.y /=n; solve(); printf("%.f\n",ans); return 0; }