1. 程式人生 > >模擬退火入門——POJ 2420

模擬退火入門——POJ 2420

題目連結:
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;
}