洛谷P1337 【[JSOI2004]平衡點 / 吊打XXX】(模擬退火)


洛谷題目傳送門

很可惜,充滿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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM