Skip to content

담금질 기법

담금질 기법은 기본적으로 문제 공간에서 무작위 방향 중 에너지를 최소화하는 경향으로 상태를 확률적으로 바꾸며 해를 탐색한다. 마치 금속을 담금질하는 것과 비슷한 원리라고 해서 Simulated Annealing(모의 담금질) 이라는 이름이 붙었다.

이를 구현하는 방식은 다양하겠지만, 시뮬레이티드 어닐링은 다음과 같이 행동한다.

  1. 현재 상태와 인접한 상태를 하나 구한다.
  2. 그 상태로 변할 때 얼마만큼 이득인지/손해인지를 판단한다.
  3. 얻을 이득과 손해의 정도에 따라 확률적으로 그 상태로 이동한다.

여기서 인접한 상태란, 현재의 상태를 국소적으로 바꾸어서 만들 수 있는 상태이다. 담금질 알고리즘은 이 과정을 반복함을 통해서 최적해를 향해 나아가고, 와중에 더 나빠지는 해에 대해서도 가끔씩은 탐색을 해보면서 지역 최적점을 피해가려 노력한다.

대략적인 과정은 아래와 같이 이뤄진다.

while ( k > 임계 온도 ){
E1 = 현재 상태의 에너지
E2 = 랜덤하게 생성한 새로운 상태의 에너지
p = exp((E1-E2)/(k*T));
if (p > rand()) {
현재 상태를 새로운 상태로 바꿈
}
k *= 온도 감률 (보통 0.95 ~ 0.9999 정도)
}

동전 뒤집기 2

담금질 기법을 사용하는 대표적인 백준 문제 중 하나로 동전 뒤집기 2가 있다.

mt19937는 랜덤 값을 생성하기 위해 사용한다.

#include <bits/stdc++.h>
using namespace std;
int n;
long long arr[33];
int main(){
mt19937 rd = mt19937((unsigned) chrono::steady_clock::now().time_since_epoch().count());
cin>>n;
uniform_int_distribution<int> ran(0, n-1);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
char c;
cin>>c;
arr[i] <<= 1;
if(c=='H') arr[i] |= 1;
}
}
int ans=n*n;
int a=0, b=0;
int prv=n*n;
double t=1.0, k=2.5;
for(int i=0;i<10101;i++){
b = a^(1<<ran(rd));
int now=0;
for(int j=0;j<n;j++){
int t=__builtin_popcount(arr[j]^b);
now+=min(t, n-t);
}
double nowP = exp((prv-now)/(k*t));
if(nowP > (double)ran(rd)/(n-1)){
a=b;
prv=now;
}
ans=min(ans, prv);
k*=0.9999;
}
cout << ans;
}

참고