[ 백준 ] 4574 / 스도미노쿠

金弘均·2021년 9월 15일
0

Baekjoon Online Judge

목록 보기
107/228
post-thumbnail

# Appreciation

/*
 * Problem :: 4574 / 스도미노쿠
 *
 * Kind :: Simulation + Backtracking
 *
 * Insight
 * - 고려해야 되는 부분들이 꽤 많다
 *   + 도미노를 어느 방향부터 채워넣을 것인가?
 *   + 현재칸과 인접한 칸이 비었는가? (도미노가 들어갈 자리가 있는가?)
 *   + 어떤 값을 가진 도미노를 거기에 넣을 수 있는가?
 *   + 상태를 어떻게 업데이트 할 것인가?
 *   + 스도쿠를 완성하지 못했는데 적절한 도미노를 넣을 수 없다면 어떻게 해야 하나?
 *
 * Point
 * - 재귀함수와 전역배열을 적극적으로 활용한다
 *   + 이 칸에는 어떤 값이 들어가 있는가?
 *   + 이 행에서는 어떤 값이 들어가 있는가?
 *   + 이 열에서는 어떤 값이 들어가 있는가?
 *   + 이 3*3 박스에서는 어떤 값이 들어가 있는가?
 *   + 지금까지 어떤 도미노를 사용했는가?
 *   # 위의 사실을 바탕으로 도미노를 넣다 뺐다 하면서 그리드를 채워나간다
 */

# Code

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

#include <iostream>
#include <cstring>

using namespace std;

#define endl '\n'

// Set up : Global Variables
int puzzle[9][9];
bool isUsed[10][10];
bool checkRow[9][10];
bool checkCol[9][10];
bool checkBox[3][3][10];
int dy[2] = {+1, 0};
int dx[2] = {0, +1};

// Set up : Functions Declaration
bool isValid(int y, int x);
void setUp(int cy, int cx, int u, int ny, int nx, int v);
void getBack(int cy, int cx, int u, int ny, int nx, int v);
bool go(int c);

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

    // Set up : Input
    int tc = 0, N;
    while (true) {
        cin >> N;
        if (N == 0) break;
        tc++;

        // Reset
        /* 전역배열 초기화 */
        memset(puzzle, 0, sizeof(puzzle));
        memset(isUsed, false, sizeof(isUsed));
        memset(checkRow, false, sizeof(checkRow));
        memset(checkCol, false, sizeof(checkCol));
        memset(checkBox, false, sizeof(checkBox));

        // Set up : Input
        for (int i=0; i<N; i++) {
            int U, V;
            string LU, LV;
            cin >> U >> LU >> V >> LV;
            puzzle[LU[0]-'A'][LU[1]-'1'] = U;
            puzzle[LV[0]-'A'][LV[1]-'1'] = V;
            isUsed[U][V] = isUsed[V][U] = true;
        }
        for (int i=1; i<=9; i++) {
            string P; cin >> P;
            puzzle[P[0]-'A'][P[1]-'1'] = i;
        }

        // Process
        /* 현재 puzzle 에 들어있는 값들을 바탕으로 상태를 갱신 */
        for (int i=0; i<9; i++) {
            for (int j=0; j<9; j++) {
                int v = puzzle[i][j];
                checkRow[i][v] = true;
                checkCol[j][v] = true;
                checkBox[i/3][j/3][v] = true;
            }
        }

        /* 도미노 채워넣기 */
        go(0);

        // Control : Output
        cout << "Puzzle " << tc << endl;
        for (int i=0; i<9; i++) {
            for (int j=0; j<9; j++) {
                cout << puzzle[i][j];
            } cout << endl;
        }
    }
}

// Helper Functions
bool isValid(int y, int x)
/* 주어진 좌표가 유효하면 true 를 반환, 그 외 false 를 반환 */
{
    return y >= 0 && y < 9 && x >= 0 && x < 9;
}

bool isPossible(int y, int x, int n)
/* 주어진 좌표 (y,x) 에 값 n 이 들어갈 수 있으면 true 를 반환, 그 외 false 를 반환 */
{
    if (checkRow[y][n]) return false;
    if (checkCol[x][n]) return false;
    if (checkBox[y/3][x/3][n]) return false;
    return true;
}

void setUp(int cy, int cx, int u, int ny, int nx, int v)
/* (cy,cx) 에 값 u 를 넣고 (ny,nx) 에 값 v 를 넣을 때 상태 갱신 -> 도미노를 놓음 */
{
    puzzle[cy][cx] = u;
    puzzle[ny][nx] = v;
    checkRow[cy][u] = true;
    checkCol[cx][u] = true;
    checkBox[cy/3][cx/3][u] = true;
    checkRow[ny][v] = true;
    checkCol[nx][v] = true;
    checkBox[ny/3][nx/3][v] = true;
    isUsed[u][v] = isUsed[v][u] = true;
}

void getBack(int cy, int cx, int u, int ny, int nx, int v)
/* (cy,cx) 에 값 u 를 넣고 (ny,nx) 에 값 v 를 뺄 때 상태 갱신 -> 도미노를 뺌 */
{
    puzzle[cy][cx] = 0;
    puzzle[ny][nx] = 0;
    checkRow[cy][u] = false;
    checkCol[cx][u] = false;
    checkBox[cy/3][cx/3][u] = false;
    checkRow[ny][v] = false;
    checkCol[nx][v] = false;
    checkBox[ny/3][nx/3][v] = false;
    isUsed[u][v] = isUsed[v][u] = false;
}

bool go(int c)
/* 도미노를 채워넣음, 방향은 왼쪽 -> 오른쪽, 위쪽 -> 아래쪽 */
{
    /* 도미노를 모두 채워넣는 데 성공함 */
    if (c == 81) return true;

    /* (int)c 를 바탕으로 현재 좌표를 알아냄 */
    int cy = c / 9;
    int cx = c % 9;
    /* 현재 좌표에 이미 도미노가 놓아져 있다면 */
    if (puzzle[cy][cx] != 0) {
        /* 다음 칸으로 넘어감 */
        return go(c+1);
    }

    /* 현재 좌표에 도미노가 없다면 */
    for (int i=0; i<2; i++) {
        /* 인접한 칸을 확인 - 아래칸, 오른쪽칸 */
        int ny = cy + dy[i];
        int nx = cx + dx[i];
        if (not(isValid(ny, nx))) continue; /* 인접칸이 유효하지 않은 좌표면 넘어감 */
        if (puzzle[ny][nx] != 0) continue; /* 인접칸에 이미 도미노가 놓아져 있으면 넘어감 */

        /* 넣고자 하는 도미노의 값을 설정 */
        for (int u=1; u<=9; u++) {
            /* (cy,cx) 에는 값 u 를 넣으려고 함 */
            for (int v=1; v<=9; v++) {
                /* (ny,nx) 에는 값 v 를 넣으려고 함 */
                /* u 와 v 가 같은 도미노는 없으므로 넘어감 */
                if (u == v) continue;
                /* u 와 v 의 값으로 이루어진 도미노를 썼었으면 넘어감 */
                if (isUsed[u][v]) continue;
                /* (cy,cx) 에 값 u 를 넣을 수 없으면 넘어감 */
                if (not(isPossible(cy, cx, u))) continue;
                /* (ny,nx) 에 값 v 를 넣을 수 없으면 넘어감 */
                if (not(isPossible(ny, nx, v))) continue;

                /* 도미노를 놓음 */
                setUp(cy, cx, u, ny, nx, v);
                /* 이후, 계속 도미노를 놓아서 그리드를 다 채워넣는데 성공했다면 */
                if (go(c+1)) {
                    /* 도미노를 빼지 않고 그 상태 그대로 함수 종료 */
                    return true;
                }
                /* 도미노를 뺌 */
                getBack(cy, cx, u, ny, nx, v);
            }
        }
    }

    /* 도미노를 놓는 데 실패 */
    return false;
}
profile
이런 미친 게임을 봤나! - 옥냥이

0개의 댓글