[ 백준 ] 2615 / 오목

金弘均·2021년 9월 16일
0

Baekjoon Online Judge

목록 보기
200/228
post-thumbnail
post-custom-banner

# Appreciation

/*
 * Problem :: 2615 / 오목
 *
 * Kind :: Simulation
 *
 * Insight
 * - 조건 1. 같은 색의 바둑알이 연속적으로 다섯 알이 놓이면 그 색이 이기게 된다
 *   조건 2. 하지만 여섯 알 이상이 연속적으로 놓인 경우에는 이긴 것이 아니다.
 *   조건 3. 아직 승부가 결정되지 않았을 경우에는 0을 출력한다.
 *   조건 4. 검은색 또는 흰색이 이겼을 경우에는 둘째 줄에 연속된 다섯 개의 바둑알 중에서
 *          가장 왼쪽에 있는 바둑알
 *          (연속된 다섯 개의 바둑알이 세로로 놓인 경우, 그 중 가장 위에 있는 것)
 *          의 가로줄 번호와, 세로줄 번호를 순서대로 출력한다
 *   + 정말... 삽질이란 삽질은 다 해본 문제이다
 *     억울해서 나름 최적화까지 했는데 거기서도 실수만발이었다
 *     # 일반적으로 이중 for 문을 이용해서
 *       왼쪽에서 오른쪽으로, 위쪽에서 아래쪽으로 바둑판의 각 점들을 방문한다고 하자
 *       오목이 가능한 경우는 총 4가지다
 *       가로로 5개, 세로로 5개, y=x 대각선으로 5개, y=-x 대각선으로 5개
 *       -> 문제는...
 *                  O ------
 *                / | \
 *               /  |  \
 *              /   |   \
 *          처음엔 이렇게 연속된 같은 개수의 알의 개수를 세려고 했는데
 *          이 경우 2번의 조건을 위배한다는 것이다
 *          검은색 바둑알이 (1,1) ~ (1,6) 까지 여섯개 놓여있다고 하면
 *          (1,1) 에서는 6개 놓인 것으로 올바르게 세지만
 *          (1,2) 에서는 5개 놓인 것으로 틀리게 세게 된다
 *          => 즉...
 *                \  |  /
 *                 \ | /
 *             ----- O -----
 *                 / | \
 *                /  |  \
 *             2번 조건을 잘 고려해주기 위해서는 위처럼
 *             각 경우마다 반대쪽 방향을 모두 세어주어야 한다
 *             가로 - 알의 왼쪽, 오른쪽
 *             세로 - 알의 위쪽, 아래쪽
 *             y=x 대각선 - 1사분면, 3사분면
 *             y=-x 대각선 - 2사분면, 4사분면
 *
 * - 그러나 이 경우 센 곳을 또 세게 되어 비효율적이다
 *   + 그래봤자
 *     바둑판 알의 개수 : 19*19
 *     가로, 세로, y=x 대각선, y=-x 대각선 : 4
 *     각 경우 확인 : 19
 *     시간복잡도 = 4 * 19^3 = 27436
 *     그냥 다 해봐도 상관없지만...
 *     # 각 경우에 다음과 같이 번호를 붙여주자
 *       가로 - 0
 *       세로 - 1
 *       y=-x 대각선 - 2
 *       y=x 대각선 - 3
 *       -> V[y][x][i] = (y,x) 알이 i번 경우에 방문었으면 true, 그렇지 않으면 false
 *          그렇다면 V[y][x][i] 를 이전에 방문했을 때
 *          그 알이 i번 경우로 승리하지 못했으면
 *          이제 i번 경우로 승리할 때 V[y][x][i] 를 사용하는 경우는 없다!
 *          는 점을 이용한 것이다 <= 결국 이전에 방문했느냐 안했느냐를 추적하는 것이다
 *          => 바둑판의 각 점을 왼쪽에서 오른쪽으로, 위에서 아래쪽으로 차례로 방문할 시
 *             이제는
 *                  O ------
 *                / | \
 *               /  |  \
 *              /   |   \
 *             V[y][x][i] 를 같이 고려한다면 위의 방향만 고려해도
 *             답을 올바르게 구할 수 있다!!!
 *
 * Point
 * - (y,x) 알에서 3번으로 승리했을 때
 *   4번의 조건에 따라
 *   y+4, x-4 를 출력해야되는 것을 주의하자
 *   + 가운데 공백도 있다... <= 이 공백때문에 또 얼마나 틀렸는지 ㅠㅠ
 */

# Code

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

#include <iostream>

using namespace std;

#define endl '\n'

// Set up : Global Variables
int N = 19;
int B[19+1][19+1];
bool V[19+1][19+1][4];
int dy[4] = {+1, +1, 0, +1};
int dx[4] = {0, +1, +1, -1};

// Set up : Functions Declaration
int isWon(int y, int x);
bool isValid(int y, int x);


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

    // Set up : Input
    for (int i=1; i<=N; i++)
        for (int j=1; j<=N; j++)
            cin >> B[i][j];

    // Process
    for (int i=1; i<=N; i++) {
        for (int j=1; j<=N; j++) {
            if (B[i][j] != 0) { /* 검은색 또는 흰색 알이 놓여있으면 */
                int res = isWon(i, j); /* 오목인지 확인 */

                // Control : Output
                if (res != -1) { /* 오목이면 */
                    cout << B[i][j] << endl; /* 검은색 xor 흰색 출력 */
                    cout << ((res == 3) ? i+4 : i); /* 3번 경우 (y=x 대각선) 예외처리 */
                    cout << ' ';
                    cout << ((res == 3) ? j-4 : j); /* 3번 경우 (y=x 대각선) 예외처리 */
                    exit(0);
                }
            }
        }
    }

    // Control : Output
    cout << 0 << endl; /* 아직 승부가 결정되지 않았음 */
}

// Helper Functions
int isWon(int y, int x)
/* (y,x) 에 놓인 바둑알이 오목인 경우 어떤 모양으로 오목인지에 따라 해당하는 번호를 반환
 * 오목이 아닌 경우 -1 을 반환
 * 가로로 오목인 경우 - 0
 * 세로로 오목인 경우 - 1
 * y=-x 대각선으로 오목인 경우 - 2
 * y=x 대각선으로 오목인 경우 - 3 */
{
    for (int i=0; i<4; i++) {
        /* (y,x) 바둑알이 이전에 i번 경우를 확인할 때 방문된적 있으면
         * (y,x) 바둑알을 이용한 i번 경우 승리는 불가능하므로 넘어감 */
        if (V[y][x][i]) continue;
        V[y][x][i] = true; /* 방문처리 */

        int cnt = 1; /* i번 경우에서 바둑알의 개수 */

        /* 바둑알 세기 */
        int ny = y + dy[i];
        int nx = x + dx[i];
        while (isValid(ny, nx) && B[ny][nx] == B[y][x] && not(V[ny][nx][i])) {
            cnt++;
            V[ny][nx][i] = true;
            ny += dy[i];
            nx += dx[i];
        }

        /* 바둑알이 5개 이면 오목이며 각 경우에 해당하는 수를 반환 */
        if (cnt == 5) return i;
    }
    /* 오목이 아님 */
    return -1;
}

bool isValid(int y, int x)
/* (y,x) 좌표가 유효하면 true 를 반환, 그 외 false 를 반환 */
{
   return y >= 1 && y <= N && x >= 1 && x <= N;
}
profile
이런 미친 게임을 봤나! - 옥냥이
post-custom-banner

0개의 댓글