1. 程式人生 > >【bzoj3680】平衡點 模擬退火

【bzoj3680】平衡點 模擬退火

ret += can std line i++ tro oid pri

模擬退火是一種求函數最值問題的隨機算法。
給定一個函數的某一初始坐標,可以擬定一個“溫度”(這裏主要是借用退火的物理意義),這裏的溫度可以理解成自變量可以取值的範圍。之後在當前最優解對應的自變量的基礎上,隨機產生一組附加量,用當前自變量加上附加量構成一個新的點,計算該點的函數值,若該點函數值比最優解還要優,則接受該情況,同時最優解和其對應的自變量坐標也跟著改變;若該點函數值沒有最優解優,則以一定概率接受它,避免無法跳出函數極值點而非最值點,這個接受的概率表示為\(e^{(+-)\Delta},\Delta=f_1-f_2\),若這個值大於隨機生成的概率,則接受並更新這個值,否則不接受。如此往復叠代,當最優解出現的區間範圍足夠小時(T溫度降到接近0時),結束叠代,得出的值就可能是最優解。
(粗體為該算法用到概率的地方)

代碼如下

#include <bits/stdc++.h>
using namespace std;
const int maxn=1010;
const double eps=1e-15;//精度
const double d=0.998;//降溫幅度
struct node{
    double x,y,w;
}p[maxn];
int n;
double ansx,ansy,t;

inline double calc(double x,double y){
    double tot=0;
    for(int i=1;i<=n;i++){
        double dx=x-p[i].x,dy=y-p[i].y;
        tot+=sqrt(dx*dx+dy*dy)*p[i].w;
    }
    return tot;
}

void SA(){
    double T=200;
    while(T>eps){
        double nowx=ansx+(2*rand()-RAND_MAX)*T;//隨機生成新坐標
        double nowy=ansy+(2*rand()-RAND_MAX)*T;
        double delta=calc(nowx,nowy)-calc(ansx,ansy);
        if(delta<0)ansx=nowx,ansy=nowy;
        else if(exp(-delta/t)*RAND_MAX>rand())ansx=nowx,ansy=nowy;//以一定概率接受
        T*=d;
    }
}

int main(){
    srand((unsigned int)time(0));

    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf%lf",&p[i].x,&p[i].y,&p[i].w);
        ansx+=p[i].x,ansy+=p[i].y;
    }
    ansx/=(double)n,ansy/=(double)n;
    SA();
    printf("%.3lf %.3lf\n",ansx,ansy);
    return 0;
}

【bzoj3680】平衡點 模擬退火