[ 백준 ] 2666 / 벽장문의 이동

金弘均·2021년 9월 16일
0

Baekjoon Online Judge

목록 보기
219/228
post-thumbnail

# Appreciation

/*
 * Problem :: 2666 / 벽장문의 이동
 *
 * Kind :: Dynamic Programming
 *
 * Insight
 * - 1번, 5번 벽장이 열려있다고 하자
 *   이제 3번 벽장을 열어야 한다면...
 *   + 2번, 3번 벽장 문을 이동시킨다 => 3번, 5번 벽장문이 열려있게 된다
 *     3번, 4번 벽장 문을 이동시킨다 => 1번, 3번 벽장문이 열려있게 된다
 *     # 이동 횟수는 열려져 있었던 벽장 문의 번호에서 열려는 벽장 문의 번호까지의 거리이다
 *       위의 예시에서는 둘다 이동 횟수는 2로 같은데 열려있는 벽장 문이 다르게 된다
 *       그리디로는 못풀겠구나...
 *       -> dp[i][a][b] = i 번째 벽장 문까지 사용하고
 *                        현재 a,b번 벽장 문이 열려져 있을 때 최소 이동횟수
 *          i 번째 사용할 d번 벽장 문을 열려고 할 때
 *          i-1 번째까지 벽장 문을 열고 현재 a,b번 벽장 문이 열려있다면
 *          => 열려 있는 a번 벽장 문을 이용해서 x번 벽장 문을 엶
 *             이제 열려 있는 벽장문은 b,d번
 *             dp[i][b][d] = min(dp[i][b][d], dp[i-1][a][b] + abs(d-a))
 *             dp[i][d][b] = min(dp[i][d][b], dp[i-1][a][b] + abs(d-a))
 *          => 열려 있는 b번 벽장 문을 이용해서 x번 벽장 문을 엶
 *             이제 열려 있는 벽장문은 a,x번
 *             dp[i][a][d] = min(dp[i][a][d], dp[i-1][a][b] + abs(d-b))
 *             dp[i][d][a] = min(dp[i][d][a], dp[i-1][a][b] + abs(d-b))
 *
 * - 열려 있는 벽장 문들의 조합을 모두 확인해야하니
 *   시간복잡도는 O(N^2) 이고
 *   총 M 개의 벽장 문을 차례로 열어야 하니
 *   총 시간복잡도는 O(MN^2) 가 된다
 *   + max(N)=max(M)=20 이니 주어진 시간 내에 충분히 풀 수 있겠다
 *
 * Point
 * - i 번째 사용할 d번 벽장 문을 열려고 할 때,
 *   현재 a,b번 벽장 문이 열려져 있다면
 *   dp[i][d][a] 와 dp[i][a][d] 또는
 *   dp[i][b][d] 와 dp[i][d][b] 를 동시에 고려해주어야 한다
 *   + dp[i][a][b] 정의 자체가 a,b번 벽장 문이 열려있는 건데
 *     이는 b,a번 벽장 문이 열려있는 것과 같고
 *     따라서 dp[i][a][b] = dp[i][b][a] 여야 하기 때문이다
 *     # 이러한 처리가 귀찮다면
 *       더 귀찮은 방법을 사용해야 한다
 *       dp[i][a][b] 에서 a<b 라는 제약을 추가해주어서
 *       int mn = min(x,a) or min(x,b)
 *       int mx = max(x,a) or max(x,b) 로 이를 다루어주어야 한다
 */

# Code

//
//  BOJ
//  ver.C++
//
//  Created by GGlifer
//
//  Open Source

#include <iostream>
#include <algorithm>

using namespace std;

#define endl '\n'

// Set up : Global Variables
#define INF 987654321

// Set up : Functions Declaration
/* None */


int main()
{
    // Set up : I/O
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    // Set up : Input
    int N; cin >> N;
    int s1, s2;
    cin >> s1 >> s2;
    int M; cin >> M;
    int D[M+1];
    for (int i=1; i<=M; i++)
        cin >> D[i];

    // Process
    /* dp[i][a][b] = i 번째 벽장 문까지 사용하고
     *               현재 a,b번 벽장 문이 열려져 있을 때 최소 이동횟수 */
    int dp[M+1][N+1][N+1];
    fill(&dp[0][0][0], &dp[M][N][N+1], INF); /* 초기값을 무한으로 초기화 */

    dp[0][s1][s2] = dp[0][s2][s1] = 0; /* 처음부터 열려진 벽장문들 */
    for (int i=1; i<=M; i++) {
        int d = D[i]; /* i 번째 열려고 하는 벽장 문의 번호 */
        for (int a=1; a<=N; a++) {
            for (int b=1; b<=N; b++) {
                /* 현재 a,b번 벽장문이 열려진 상태 */
                if (a == b) continue; /* a 와 b 는 같을 수 없음 */
                /* a 번 벽장 문을 닫고 d 번 벽장 문을 엶 */
                dp[i][d][b] = min(dp[i][d][b], dp[i-1][a][b] + abs(a-d));
                dp[i][b][d] = min(dp[i][b][d], dp[i-1][a][b] + abs(a-d));
                /* b 번 벽장 문을 닫고 d 번 벽장 문을 엶 */
                dp[i][d][a] = min(dp[i][d][a], dp[i-1][a][b] + abs(b-d));
                dp[i][a][d] = min(dp[i][a][d], dp[i-1][a][b] + abs(b-d));
            }
        }
    }

    /* 마지막에 주어진 벽장 문까지 모두 열었을 때
     * 열려진 벽장 문들의 가능한 조합을 모두 탐색하여 최소 이동횟수를 구함 */
    int ans = INF;
    for (int i=1; i<=N; i++) {
        for (int j=i+1; j<=N; j++) {
            ans = min(ans, dp[M][i][j]);
        }
    }

    // Control : Output
    cout << ans << endl;
}

// Helper Functions
/* None */
profile
이런 미친 게임을 봤나! - 옥냥이

0개의 댓글