알고리즘/문제 풀이

백준 23353번: 승부 조작

Themion 2022. 1. 14. 20:00

https://www.acmicpc.net/problem/23353

 

23353번: 승부 조작

첫째 줄에 자연수 $N$이 주어진다. ($2 \le N \le 1,000$) 둘째 줄부터 $N$개 줄에는 줄마다 $N$개의 숫자가 공백으로 구분되어 주어진다. 이는 랑이가 돌을 바꿔치기하기 전 바둑판의 상태를 나타낸다.

www.acmicpc.net

각 좌표를 포함하면서 연속으로 존재하는 흑돌의 길이를 흑돌의 방향(가로, 세로, 대각선)별로 분류하여 저장한다면, 어느 한 백돌을 흑돌로 바꿨을 때 백돌의 주변에 존재하는 흑돌을 포함하는 연속된 흑돌의 길이는 O(1)번만에 계산할 수 있으므로, 각 백돌을 흑돌로 바꿨을 때 갱신되는 흑돌의 길이는 각 방향으로 백돌 앞뒤의 연속 흑돌 길이의 합 + 1로 빠르게 계산할 수 있다.

#include <iostream>
#include <queue>

using namespace std;

// 각 백돌의 좌표 (y, x)를 pair로 저장해 큐에 push
typedef pair<int, int> coord;
typedef queue<coord> que;

#define MAX_N 1000

// dir 방향으로 다음 칸의 방향을 매크로를 이용해 계산
#define dY add[dir][0]
#define dX add[dir][1]
// NOW, PREV, POST: 각각 dir 방향으로 현재 좌표, 이전 좌표, 다음 좌표의 연속값
#define NOW score[dir][y][x]
#define PREV score[dir][y - dY][x - dX]
#define POST score[dir][y + dY][x + dX]

//0: 세로
//1: 가로
//2: 기울기 1 대각
//3: 기울기 -1 대각

// N: 게임판의 크기, ans: 최장 연속 흑돌의 길이
// ans: { 가로, 세로, 기울기 1 대각선, 기울기 -1 대각선}[dir]방향 변위
int N, ans = 0, add[4][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1} };
// board: 냥목 게임판
// score[dir][y][x]: 게임판에서 좌표 (y, x)를 포함한 dir 방향의 연속된 흑돌의 길이
short board[MAX_N][MAX_N], score[4][MAX_N][MAX_N];

int max(int a, int b) { return a > b ? a : b; }

// 좌표 (y, x)가 바둑판 위에 존재한다면 true, 아니라면 false
bool is_valid(int y, int x) { return y >= 0 && y < N&& x >= 0 && x < N; }

// 좌표 (y, x)를 포함하는 dir 방향의 연속된 흑돌의 길이를 반환
int set_score(int dir, int y, int x) {
    // 아직 흑돌의 길이를 계산하지 못했다면
    if (!NOW) {
        // 이전 좌표가 존재하는 좌표값이라면 현재 연속값을 아래와 같이 설정한다
        // 현재 좌표에 흑돌이 있다면 이전까지의 연속값 + 1,
        //                   없다면 0
        if (is_valid(y - dY, x - dX)) 
            NOW = board[y][x] == 1 ? PREV + 1 : 0;
        // 만일 연속값이 0이 아니고 다음 좌표가 존재한다면
        // 다음 값을 재귀적으로 계산한 뒤 현재값과 비교해
        // 더 큰 값을 현재 연속값으로 갱신
        if (NOW && is_valid(y + dY, x + dX))
            NOW = max(NOW, set_score(dir, y + dY, x + dX));
    }
    // 이전에 계산한 흑돌의 길이와 현재 흑돌의 길이를 비교
    ans = max(ans, NOW);
    // 계산된 흑돌의 길이를 반환
    return NOW;
}

int main() {
    // 입출력 속도 향상
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    // y, x: 각 좌표, chk: 백돌을 흑돌로 바꿨을 때 갱신된 흑돌의 연속값
    int y, x, chk;
    // q: 백돌의 좌표의 집합
    que q;

    // 게임판의 상태를 입력받으면서 백돌의 좌표를 q에 push
    cin >> N;
    for (y = 0; y < N; y++) for (x = 0; x < N; x++) {
        cin >> board[y][x];
        if (board[y][x] == 2) q.push({ y, x });
    }

    // 게임판의 좌우 및 위쪽 모서리의 초기 연속값틀 해당 좌표의 흑돌이 있는지로 설정
    for (int i = 0; i < N; i++) {
        score[1][i][0] = score[2][i][0] = (board[i][0] == 1);
        score[0][0][i] = score[2][0][i] = score[3][0][i] = (board[0][i] == 1);
        score[3][i][N - 1] = (board[i][N - 1] == 1);
    }

    for (int i = 0; i < N; i++) {
        // 가로 및 세로 연속값을 계산
        for (int j = 1; j < N; j++) {
            set_score(0, j, i);
            set_score(1, i, j);
        }

        // 기울기가 1인 흑돌의 연속값을 계산
        for (int j = 1; i + j < N; j++) {
            set_score(2, i + j, j);
            set_score(2, j, i + j);
        }

        // 기울기가 -1인 흑돌의 연속값을 계산
        for (int j = 1; i + j < N; j++) {
            set_score(3, j, N - 1 - i - j);
            set_score(3, i + j, N - 1 - j);
        }
    }

    // 모든 백돌에 대해
    while (!q.empty()) {
        // 백돌의 좌표를 q에서 가져온 뒤
        coord c = q.front();
        q.pop();

        // 매크로를 위해 변수 y, x에 저장
        y = c.first; x = c.second;
        // 각 방향에 대해
        for (int dir = 0; dir < 4; dir++) {
            // 백돌을 흑돌로 바꾸었으므로 연속값의 최솟값은 반드시 1
            chk = 1;
            // 이전 좌표값과 다음 좌표값이 존재한다면 해당 연속값을 더한다
            if (is_valid(y - dY, x - dX)) chk += PREV;
            if (is_valid(y + dY, x + dX)) chk += POST;
            // 최댓값 갱신
            ans = max(ans, chk);
        }
    }

    // 가장 큰 연속된 흑돌의 길이를 출력
    cout << ans << '\n';

    return 0;
}