模擬退火學習筆記

HANGRY_Sol&Cekas發表於2024-07-28

模擬退火學習筆記

前言

不知道為啥突然有閒情學這個...

模擬退火 (Simulated Annealing) , 簡稱 \(SA\) .

是一種基於隨機化的演算法,無門檻,主要是為了騙分...

不是正解!!!!

根據 爬山演算法 的過程,我們發現:對於一個當前最優解附近的非最優解,爬山演算法直接捨去了這個解。而很多情況下,我們需要去接受這個非最優解從而跳出這個區域性最優解,即為模擬退火演算法。

關於退火

什麼是退火?

退火是一種金屬熱處理工藝,指的是將金屬緩慢加熱到一定溫度,保持足夠時間,然後以適宜速度冷卻。目的是降低硬度,改善切削加工性;消除殘餘應力,穩定尺寸,減少變形與裂紋傾向;細化晶粒,調整組織,消除組織缺陷。準確的說,退火是一種對材料的熱處理工藝,包括金屬材料、非金屬材料。而且新材料的退火目的也與傳統金屬退火存在異同。

賀的百度百科

\(E| \{x_i\} |\) 表示某一物質體系在微觀狀態 \(\{x_i\}\) 下的內能,對於給定溫度 \(T\) , 若體系處於熱平衡態時, \(E |\{x_i\}|\) 服從 Boltzmann 分佈:

\[f = c(T) \times e ^ {- \frac{E|\{x_i\}|}{k \times T}} \]

其中 \(k\) 為 Boltzmann 常數。
\(T\) 下降, \(E\) 隨之下降。只要溫度下降足夠慢,體系可以長久保持熱平衡態。

當我們退火時,剛開始的時候溫度高,分子運動劇烈,變化率較高;

然而對於溫度降下來之後,分子逐漸趨於穩定,於是分子變化比較小。

模擬退火,就是模擬的這個過程。

具體實現

當然,什麼都不能擺脫隨機化。

我們設初始溫度 \(T_0 > 1000\) , 降溫係數 \(\zeta = \{0.996 , 0.995 , \dots\}\) , 末尾溫度 \(T_k > 0\) , 在 \(10 ^ {-15}\) 左右 .

對於我們的新橫座標來說,顯然他要有區域性的特性,並隨時間的延長,溫度下降,轉移地方要更加穩定,就是離舊的橫座標近一點,因此在隨機時要有 \(T\) 這個自變數。

然而對於我們新的這個值 \(y\) , 舊的值為 \(x\) , \(\Delta = y - x\)

退火的過程、轉移的機率如下:

\[P(x , y) = \begin{cases} 1 \ , \ \Delta < 0 \\ e ^ {- \frac{\Delta}{T}} \ , \ \Delta > 0 \end{cases}\]

為什麼會存在第二種轉移呢?

是為了跳出區域性最優解,可能進入新的更優解的地方。

貼圖:

具體實現 [JSOI2004] 平衡點 / 吊打XXX

題目描述

如圖,有 \(n\) 個重物,每個重物系在一條足夠長的繩子上。

每條繩子自上而下穿過桌面上的洞,然後系在一起。圖中 \(x\) 處就是公共的繩結。假設繩子是完全彈性的(即不會造成能量損失),桌子足夠高(重物不會垂到地上),且忽略所有的摩擦,求繩結 \(x\) 最終平衡於何處。

注意:桌面上的洞都比繩結 \(x\) 小得多,所以即使某個重物特別重,繩結 \(x\) 也不可能穿過桌面上的洞掉下來,最多是卡在某個洞口處。

\(1\le n\le 1000\)

\(-10000\le x_i,y_i\le10000, 0<w_i\le1000\)

題解

假設平衡點座標為 \((X , Y)\)

那麼對於一個重物(座標為 \(x_i , y_i\) )貢獻的重力勢能為:

\(E = \sqrt{(X - x_i) ^ 2 + (Y - y_i) ^ 2} \times w_i\)

我們是需要將所有的勢能和最小化即可。

注意因為我們為了讓答案儘可能的對, \(SA\) 過程要跑很多遍。

CODE
#include <bits/stdc++.h>
using namespace std ;
#define int long long 
const int N = 2e5 + 100 ; 
inline int read() {
	int x = 0 , f = 1 ; 
	char c = getchar() ; 
	
	while (c < '0' || c > '9') {
		if (c == '-') f = -f ; 
		
		c = getchar() ; 
	}
	
	while (c >= '0' && c <= '9') {
		x = x * 10 + c - '0' ; 
		c = getchar() ; 
	}
	
	return x * f ; 
}

struct Physics {
	int x , y , gravity ; 
} a[N] ; 
int n ; double first , second , ans = 1000000000 , averx , avery ; 

void Energy(double x , double y , double & statics) {
	statics = 0 ; 
 
	for (int i = 1 ; i <= n ; ++ i) {
		statics += sqrt((x - a[i].x) * (x - a[i].x) + (y - a[i].y) * (y - a[i].y)) * a[i].gravity ; 
	}
}

void SA() {
	double T = 3000 ; 
	double x = first , y = second , now , delta_E ; 
	while (T > 1e-16) {
		x = first + (rand() * 2ll - RAND_MAX) * T ; 
		y = second + (rand() * 2ll - RAND_MAX) * T ; 
		Energy(x , y , now) ; delta_E = now - ans ; 

		if (delta_E < 0) {
			ans = now , first = x , second = y ; 
		} else if (exp(- delta_E / T) * RAND_MAX > rand()) {
			first = x , second = y ; 
		}

		T *= 0.996 ; 
	}
}

inline void solve() {
	SA() ; SA() ; SA() ; SA() ; SA() ; 
	SA() ; SA() ; SA() ; SA() ; SA() ; 
	SA() ; SA() ; SA() ; SA() ; SA() ; 
	SA() ; SA() ; SA() ; SA() ; SA() ; 
	SA() ; SA() ; SA() ; SA() ; SA() ; 
}

double sumx , sumy ; 
signed main() {
	srand((unsigned long long)(new char)) ; 
	n = read() ; 
	for (int i = 1 ; i <= n ; ++ i) {
		a[i] = {read() , read() , read()} ; 
		sumx += a[i].x ; sumy += a[i].y ; 
	}

	averx = 1.0 * sumx / n ; 
	avery = 1.0 * sumy / n ; 

	Energy(averx , avery , ans) ; 
	first = averx , second = avery ; 
	solve() ; 

	printf("%.3lf %.3lf" , first , second) ; 
}

結尾撒花 \(\color{pink}{✿✿ヽ(°▽°)ノ✿}\)

相關文章