ps/알고리즘

99클럽 코테스터디 36일차 TIL BOJ- 도미노

nastorond 2024. 8. 26. 22:01

도미노

BOJ Gold V BruteForce
문제 링크

문제 설명

세준이가 가지고 있는 도미노는 약간 다르다. 세준이는 도미노를 N2개 가지고 있다. 따라서 N=2라면, 세준이는 (1,1), (1,2), (2,1), (2,2) 이렇게 총 N2개를 가지고 있는 것이다.

세준이는 이 도미노를 가지고 도미노미도마도라는 게임을 하려고 한다. 이 게임은 김민오가 만들었다.

이 게임에서 도미노는 N*N크기의 보드에 놓여져 있다. i번째 행, j번째 열에는 (i,j)라고 쓰여 있는 도미노가 놓여져 있다. 플레이어는 도미노를 정확하게 N개를 골라야 하는데, 선택한 도미노를 두 개가 같은 행에서 고르고, 선택한 도미노를 같은 열에서 고르면 안 된다는 조건이 있다. 또, 고른 도미노를 가지고 사이클을 만들 수 있다. 사이클을 만드는 방법은, 도미노 A와 B가 있을 때, A의 두 번째 숫자와 B의 첫 번째 수가 같으면 된다. 그리고 사이클을 이루는 첫 번째 도미노의 처음 숫자와 마지막 도미노의 둘째 숫자가 같으면 된다.

예를 들어, (1,3), (3,2), (2,4), (4,1)을 골라서 사이클을 만들 수 있다.

N개의 도미노를 고르면 이러한 사이클이 한 개 또는 그 이상의 그룹이 나온다. (1,1)와 같은 도미노는 자기 자신으로 사이클을 이루므로 하나의 그룹이다.

게임의 조건 중에 각 행과 열에서 중복되면 안되는 조건이 있기 때문에, 항상 사이클을 이룰 수 있다.

모든 도미노는 그 뒷면에 숫자가 쓰여 있다. 이 게임에서 점수를 계산할 때는 자기가 고른 도미노의 뒷면에 쓰여 있는 수를 모두 곱한다. 그 다음에 만약 사이클 그룹의 개수가 짝수가 되면 그 수에 -1을 곱한다.

세준이는 자기가 이 게임에서 얻을 수 있는 최대 점수와 최소 점수가 궁금해 졌다.

도미노의 개수와 도미노 뒷면에 쓰여 있는 수가 주어질 때, 세준이가 얻을 수 있는 최대 점수와 최소 점수를 구하는 프로그램을 작성하시오.

입력
첫째 줄에 N이 주어진다. N은 6보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에 각 도미노에 쓰여 있는 수가 주어진다. i행 j열에 쓰여 있는 수는 도미노 (i,j)의 뒷 면에 쓰여 있는 수이다. 도미노의 뒷면에는 0부터 9까지의 수와 A부터 I까지 알파벳 대문자가 쓰여 있고, A부터 I까지 문자가 의미하는 것은 -1부터 -9까지이다.

출력
첫째 줄에 세준이가 얻을 수 있는 최소 점수, 둘째 줄에 세준이가 얻을 수 있는 최대 점수를 출력한다.

행과 열이 겹치지 않도록 숫자를 고르면서 최대 최소 값을 고르는 문제
사이클이란 하나의 숫자의 열 인덱스와 다른 하나의 숫자의 행의 인덱스가 같고 첫번째 숫자의 행 인덱스와 마지막 숫자의 열 인덱스가 같으면 된다.
(1, 3), (2, 2), (3, 1) 일 경우 사이클이 두개 발생한다고 생각하면 된다.

문제 풀이

문제를 이해하는 것이 가장 어려웠어서 문제를 이해하면 다음은 비교적 쉬웠다.
숫자의 범위 자체가 그리 크지않고, 주어지는 크기도 6^2 라서 완전탐색을 하면 되는 문제였다.

코드

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int dominoes[7][7], N, resMax=-1e9, resMin=1e9;
bool visited[7] = {false, };

void ckAll(int curRow, int value, vector<pair<int, int>>& par) {
    if (curRow == N) {
        int cycleCnt = 0;
        vector<bool> nodeVisited(N, false);

        for (int i = 0; i < N; i++) {
            if (!nodeVisited[i]) {
                int current = i;
                while (!nodeVisited[current]) {
                    nodeVisited[current] = true;
                    int next = par[current].second - 1;
                    if (next == current) break;
                    current = next;
                }
                cycleCnt++;
            }
        }

        if (cycleCnt % 2 == 0 && cycleCnt > 0) {
            value *= -1;
        }

        resMax = max(value, resMax);
        resMin = min(value, resMin);
        return;
    }

    for (int i=0; i<N; i++) {
        if (!visited[i]) {
            visited[i] = true;
            par.push_back(make_pair(curRow+1, i+1));
            ckAll(curRow+1, value*dominoes[curRow][i], par);
            visited[i] = false;
            par.pop_back();
        }
    }
}

int main () {
    ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);

    cin >> N;
    string inputText;
    if (N == 1) {
        cin >> inputText;
        if ('A' <= inputText[0] && inputText[0] <= 'I') {
            cout << -1 * (inputText[0] - 'A' + 1) << "\n";
            cout << -1 * (inputText[0] - 'A' + 1) << "\n";
        } else {
            cout << -1 * (inputText[0] - '0') << "\n";
            cout << -1 * (inputText[0] - '0') << "\n";
        }
        return 0;
    }

    for (int i=0; i<N; i++) {
        cin >> inputText;
        int cnt=0;
        for (char c: inputText) {
            if (0 > c - 'A') {
                dominoes[i][cnt] = c - '0';
            } else {
                dominoes[i][cnt] = -(c-'A'+1);
            }
            cnt++;
        }
    }

    pair<int, int> tmp;
    vector<pair<int, int>> tmpV;
    // tmpV.push_back(tmp);
    ckAll(0, 1, tmpV);

    cout << resMin << "\n";
    cout << resMax << "\n";

    return 0;
}

회고

문제를 이해하면 이 문제에 대해서는 반은 해결한거나 다름 없었다 생각한다.
나는 문제가 잘 이해되지 않아 gpt 한테 예제를 보여줘 가며 설명해달라고 했고, 한시간에 걸쳐서 이해했다.
사이클의 대한 조건을 따지는 것이 조금 어려운 문제였다.