1. 程式人生 > >BZOJ3680:吊打XXX

BZOJ3680:吊打XXX

傳送門 cal esp 最終 模擬 計算 理解 ret pro

我對模擬退火的理解:https://www.cnblogs.com/AKMer/p/9580982.html

題目傳送門:https://www.lydsy.com/JudgeOnline/problem.php?id=3680

模擬退火在計算幾何方面有很大的用處,特別是求費馬點一類的問題。

題目意思就是要你找平面上的廣義費馬點。

所謂費馬點,就是在一個\(n\)邊形內,這個點如果到\(n\)個頂點距離和最小,那麽它就是這個\(n\)邊形的費馬點。

然後廣義費馬點就是頂點上帶權的費馬點,統計的時候距離要乘上這個權值再加起來。

我們靈性的腦補一波就可以發現,這個題目顯然不存在局部最優解,所以我們可以大膽爬山。

然後我們再靈性的想一想:首先我們可以把初始點定在\(n\)

個點的中心位置,這樣可以減少轉移次數。再者,一個繩結如果不在最終目標點的話,那麽它肯定有往最終目標點移動的趨勢。所以我們在溫度很高的時候,繩結坐標的跳動距離就設置大一點,就先向趨勢所在的方向跳。慢慢的溫度低了,我們就跳小一點的步子,慢慢穩定下來,這樣子最後甚至可以準確的命中正確答案!

然後因為這個性質,爬山算法就比模擬退火在這道題上優秀得多了。爬山算法可以直接在\(BZOJ\)\(AC\),但是模擬退火不行。

畢竟沒有局部最優解你還接受更加差的狀態就顯得很智障了……

時間復雜度:\(O(能A)\)

空間復雜度:\(O(能A)\)

爬山算法\(AC\)代碼如下:

#include <cmath>
#include <ctime>
#include <cstdio>
#include <algorithm>
using namespace std;
 
#define sqr(a) ((a)*(a))
 
const int maxn=1e4+5;
 
int n;
double ansx,ansy,ans=1e18;//ans記錄最小權值,ansx記錄歷史最優節點的x,ansy記錄歷史最優節點的y
 
struct gty {
    double x,y,w;//x,y存坐標,w存重量
}point[maxn];
 
double len() {
    double x=rand()%200000-100000;
    return x/100000;
}//隨機一個-100000~100000之間的長度
 
double dis(double x1,double y1,double x2,double y2) {
    return sqrt(sqr(x1-x2)+sqr(y1-y2));
}//求(x1,y1),(x2,y2)兩點之間的距離
 
double calc(double x,double y) {//計算以點(x,y)為繩結時候的權值
    double tmp=0;
    for(int i=1;i<=n;i++)
        tmp+=dis(x,y,point[i].x,point[i].y)*point[i].w;//直接暴力算
    if(tmp<ans) {ans=tmp;ansx=x,ansy=y;}//如果權值比歷史最優更小,那麽就更新歷史最優
    return tmp;//返回權值
}
 
int main() {
    srand(time(0));
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%lf%lf%lf",&point[i].x,&point[i].y,&point[i].w);
        ansx+=point[i].x;ansy+=point[i].y;
    }ansx/=n;ansy/=n;double now_x=ansx,now_y=ansy;//讀入以及初始化
    for(double T=10000;T>=1e-5;T*=0.98) {
        double nxt_x=now_x+len()*T,nxt_y=now_y+len()*T;//爬山,每次跳len*T那麽長,那麽隨著溫度慢慢降低也會趨向穩定
        if(calc(now_x,now_y)>calc(nxt_x,nxt_y))
            now_x=nxt_x,now_y=nxt_y;//如果這個方向是繩結移動的趨勢方向那麽就直接跳過去
    }
    printf("%.3lf %.3lf\n",ansx,ansy);//輸出
    return 0;
}

模擬退火不能\(AC\)代碼如下:

#include <cmath>
#include <ctime>
#include <cstdio>
#include <algorithm>
using namespace std;

#define sqr(a) ((a)*(a))

const int maxn=1e4+5;
const double T_0=1e-5;
const double del_T=0.98;

int n;
double ansx,ansy,ans=1e18;

struct gty {
    double x,y,w;
}point[maxn];

double dis(double x1,double y1,double x2,double y2) {
    return sqrt(sqr(x1-x2)+sqr(y1-y2));
}

double calc(double x,double y) {
    double tmp=0;
    for(int i=1;i<=n;i++)
        tmp+=dis(x,y,point[i].x,point[i].y)*point[i].w;
    if(tmp<ans) {ans=tmp;ansx=x,ansy=y;}
    return tmp;
}

double len() {
    double x=rand()%200000-100000;
    return x/100000;
}

void Anneal() {
    double T=1e4,now_x=ansx,now_y=ansy;
    while(T>=T_0) {
        double nxt_x=now_x+len()*T;
        double nxt_y=now_y+len()*T;
        double tmp1=calc(now_x,now_y);
        double tmp2=calc(nxt_x,nxt_y);
        if(tmp2<tmp1||exp((tmp1-tmp2)/T)*RAND_MAX>rand())//除了這裏和爬山算法是一模一樣的。這裏加了一個模擬退火特有的“接受不優於當前狀態的狀態”的概率
            now_x=nxt_x,now_y=nxt_y;
        T*=del_T;
    }
}

int main() {
    srand(time(0));
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%lf%lf%lf",&point[i].x,&point[i].y,&point[i].w);
        ansx+=point[i].x,ansy+=point[i].y;
    }ansx/=n,ansy/=n;
    for(int i=1;i<=54;i++)Anneal();
    printf("%.3lf %.3lf\n",ansx,ansy);
    return 0;
}

BZOJ3680:吊打XXX