문제 링크
- http://icpc.me/16992
사용 알고리즘
- Simulated Annealing
풀이
3-SAT 문제는 NP-Complete에 속하므로 다항 시간 안에 해결하는 알고리즘이 알려져 있지 않습니다. 적절한 휴리스틱을 이용해 문제를 풀어봅시다.
제 풀이는 기본적으로 Simulated Annealing을 사용합니다. SA에 대한 설명은 계절학교와 구글 해시코드에서 놀라운 휴리스틱 실력을 보여주었던 Ryute님의 설명으로 대신합니다.
제가 처음 시도한 풀이는 다음과 같습니다.
- $N, M$이 매우 작으면($M\cdot2^N \leq 3\cdot10^8$정도?) Bruteforcing으로 답을 찾는다.
- 최대한 다양한 상수(랜덤 시드, 볼츠만 상수, 초기 온도, 온도 변화율, 상태 전이 개수 등)를 이용해 Simulated Annealing을 시도한다.
- 맞은 테스트케이스의 합집합이 최대한 많은 부분을 커버하도록 코드를 잘 조합한다.
(3)은 이 문제가 BOJ에서 전체 채점 문제이고 채점 순서가 고정되어 있기 때문에 가능합니다. (3)을 실현하기 위해서는 똑같은 결과를 재현할 수 있어야 하므로, 모든 랜덤 시드는 손으로 직접 입력해야 합니다.
여기까지 오면 19x점 정도를 얻을 수 있습니다. 초기 상태를 모두 false으로 지정해서 그런지 성능이 별로 안 좋았습니다. 그래서 초기 상태를 지정하는 것도 여러 가지 방법을 시도해 보았습니다.
- 모두 false으로 초기화 (
ZeroInitializer
) - 랜덤으로 초기화 (
RandomInitializer
) - clause에 true로 등장하는 횟수가 많으면 true, false로 등장하는 횟수가 더 많으면 false로 초기화 (
StrangeInitializer
)
여러 초기화 전략을 시도하니 200 ~ 203점을 받게 되었습니다. 여기까지 작성한 코드는 다음과 같습니다. 앞서 말한 것처럼, 실행 결과를 재현할 수 있도록 랜덤 시드를 직접 파라미터로 넘깁니다.
1 |
|
여기까지 오면 보통 28, 41, 128번 테스트케이스 같은 곳에서 한 두 개 정도 틀릴텐데, 이런 부분을 직접 잡는 것은 너무 힘들어서 랜덤을 믿기로 했습니다.RandomInitializer
를 이용해 초기화를 한 뒤, 처음으로 false가 되는 clause에 관여하는 변수 하나를 뒤집는 연산(flipHeuristic
)을 100번 정도 수행하는 것을 시간 제한이 끝나기 직전까지 반복합니다.
1 |
|
RandomSearch
까지 추가했더니 204개의 테스트케이스를 모두 맞을 수 있었습니다.
아래 코드는 랜덤 시드가 고정되어있기 때문에, 그대로 제출하면 글을 업로드한 2021년 9월 기준으로 모든 테스트케이스에 대해 올바른 답을 낼 수 있음이 보장됩니다.
전체 코드
1 |
|