Algorithm/분할정복

BOJ#11729 하노이 탑 이동 순서

밤이2209 2017. 5. 9. 00:01

BOJ#11729 하노이 탑 이동 순서


* 문제



* 풀이

<BFS + 비트마스크>

처음에는 bfs + 비트마스크를 이용하여 풀었습니다.
하노이탑의 각 상태를 비트마스크를 이용하여 long 정수 하나로 구현하고, bfs 탐색을 해가면서 목표 정점을 찾는 방식
(각 원판은 3개의 장대 중 하나에 존재할 수 있으므로 원판의 위치는 2비트로 표현할 수 있습니다.)

그러나 정점의 최대 개수가 3^20이고, 각 정점마다 3^2 loop를 통해 탐색을 하므로
총 시간복잡도 3^22로 시간초과 발생합니다.

,

<분할 정복법>

- 분할 정복법 : 그대로 해결할 수 없는 문제를 작은 문제로 분할하여 해결하고, 그 결과들을 합병하면서 본 문제를 해결하는 방식
- 단점 : 보통 재귀 방식으로 구현되는데, 재귀 함수 호출 오버헤드가 부담된다면 다른 방식으로도 구현할 수 있다.

원판이 N개일 때, 1번 장대에서 3번 장대로 모든 원판을 이동하려면
  1) N-1개의 원판을 2번 장대로 모두 옮긴다.
  2) 하나 남은 원판을 3번 장대로 옮긴다.

원판이 N-1개일 때,  2번 장대에서 3번 장대로 모든 원판을 이동하려면
(... 반복)




* 나의 코드

재귀, 분할정복

bfs + 비트마스크


import java.io.*;

/**
* BOJ#11729 하노이 탑 이동 순서
* https://www.acmicpc.net/problem/11729
*/

public class Main {

static BufferedReader br;
static BufferedWriter bw;

public static void main(String[] args) throws IOException {

int N;

// input
br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new OutputStreamWriter(System.out));

N = Integer.parseInt(br.readLine());

// solve
bw.write((int) Math.pow(2, N) - 1 + "\n");

solve(N, 1, 3);

bw.flush();

bw.close();
br.close();

} // ~ main

static void solve(int N, int from, int to) throws IOException {

if (N == 0) return;

solve(N - 1, from, 6 - from - to);
bw.write(from + " " + to + "\n");
solve(N - 1, 6 - from - to, to);
}
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;

/**
* BOJ#11729 하노이 탑 이동 순서
* https://www.acmicpc.net/problem/11729
*
* bfs + 비트마스크로 최소 이동 횟수 구하기
*/

public class Main2 {

static final int EMPTY = -1;

public static void main(String[] args) throws IOException {

int N; // 원판의 개수
long state = 0L; // 현재 상태
long endState = 0L; // 목표 상태
HashSet<Long> discovered = new HashSet<>(); // 정점 중복 방문 처리

// input
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

N = Integer.parseInt(br.readLine());

// 장대는 0번부터 2번까지 있다 (3개)
// 원판은 0번부터 N-1번까지 있다 (N개)

// 목표 상태 : 모든 원판이 2번 장대에 있는 경우
for (int i = 0; i < N; i++) {

endState <<= 2;
endState |= 2;
}

// solve - bfs
int cnt = -1;
Queue<Long> queue = new LinkedList<>();

queue.add(state);
discovered.add(state);

while (!queue.isEmpty()) {

cnt++;

int size = queue.size();
for (int i = 0; i < size; i++) {

long curState = queue.poll();

// 종료조건
if (curState == endState) {

System.out.println(cnt);
return;
}

// 게임판 변환
// 각 장대의 Top에 위치한 원판(Piece)을 구한다
int[] top = {EMPTY, EMPTY, EMPTY};
for (int j = N - 1; j >= 0; j--) {

top[(int) get(curState, j)] = j;
}

// 탐색
// j번 장대의 top을 k번 장대로 옮긴다
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {

// 두 장대가 같은 경우
if (j == k) continue;
// j번 장대에 원판이 없는 경우
if (top[j] == EMPTY) continue;

// j번 장대의 top이 k번 장대의 top보다 큰 경우
if (top[k] != EMPTY && top[j] > top[k]) continue;

long nextState = set(curState, top[j], k);

// 이미 방문한 정점인 경우
if (discovered.contains(nextState)) continue;

queue.add(nextState);
discovered.add(nextState);
}
}
}
}

} // ~ main

static long get(long state, int index) {

// (index)번 원판의 위치를 반환
return (state >> (index * 2)) & 3;
}

static long set(long state, int index, int value) {

// (index)번 원판의 위치를 지우고 -> (index)원판의 위치를 value로 기록한다
return (state & ~(3 << (index * 2))) | (value << (index * 2));
}
}


'Algorithm > 분할정복' 카테고리의 다른 글

BOJ#1992 쿼드트리  (0) 2017.08.25
BOJ#1074 Z  (0) 2017.08.24