[ 백준 ] 6987 / 월드컵

金弘均·2021년 9월 16일
0

Baekjoon Online Judge

목록 보기
192/228
post-thumbnail

# Appreciation

/*
 * Problem :: 6987 / 월드컵
 *
 * Kind :: Backtracking
 *
 * Insight
 * - 6개의 팀, 총 경기횟수는 15번
 *   가능한 경우의 수는 3^15 = 14348907
 *   + 위에서 연산 딱 10번씩만 더한다 치면... 10^8 을 넘어서 시간초과가 날 것이다
 *     # 다 만들어보는 것이 이 문제를 푸는 올바른 방법이 아닐 수도 있겠다 <- 실제로 해보니 안됐다
 *       -> 모든 결과를 다 구한 후 주어지는 결과가 모든 결과에 있는지 살펴보는 게 아니라
 *          주어지는 결과가 유효한지를 알아봐야 겠다
 *
 * - 그럼 뭐... 백트래킹이지
 *   + 주어진 결과로부터 각 경기의 가능한 결과를 생각해보자
 *     # A팀과 B팀의 경기결과가 무승부라면
 *       A팀의 무승부 수와 B팀의 무승부 수가 0 보다 커야 한다
 *       -> A팀의 무승부 수와 B팀의 무승부 수에서 각각 1 씩 뺀 다음,
 *          다음 경기인 A팀과 C팀의 경기결과를 살펴보자
 *     # A팀과 B팀의 경기결과가 A팀 승리라면
 *       A팀의 승리 수와 B팀의 패배 수가 0 보다 커야 한다
 *       -> A팀의 승리 수와 B팀의 패배 수에서 각각 1 씩 뺀 다음,
 *          다음 경기인 A팀과 C팀의 경기결과를 살펴보자
 *     # A팀과 B팀의 경기결과가 B팀 승리라면
 *       A팀의 패배 수와 B팀의 승리 수가 0 보다 커야 한다
 *       -> A팀의 패배 수와 B팀의 승리 수에서 각각 1 씩 뺀 다음,
 *          다음 경기인 A팀과 C팀의 경기결과를 살펴보자
 *
 * - 각 팀의 승리, 무승부, 패배 수가 줄어들면서
 *   어느 하나라도 0 이 되면
 *   그 이후 가능한 선택지가 확 줄어든다
 *   + 실제로 위의 검증방식이라면 가장 시간이 오래 걸리는 입력은
 *     2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 (2=12개, 1=6개) 일텐데
 *     # 이때 결과를 검증하는 함수가 몇 번 호출되는지 알아본 결과
 *       총 10183 번 호출되었다
 *       -> 예상보다 훨씬 적네...?
 *
 * Point
 * - 승리, 무승부, 패배 수가 100, 1000 도 들어올 수 있다 (문제에서 딱히 범위가 정해져있지 않음!)
 *   + 각 수가 1 이상, 6 이하인지와
 *     총 경기횟수가 15번 이므로 각 팀의 (승리, 무승부, 패배) 한 횟수의 총합은 30 이 되어야 한다
 *     # 이를 모두 검사해주고 그 다음에 논리적으로 가능한지 살펴보자
 *
 * - 총 경기횟수가 15번 밖에 되지않기 때문에
 *   그냥 순서대로 어느팀끼리 경기를 하는지 사전에 정해주면 편하다
 *   + 그렇지 않으면... 무슨 팀이 무슨 팀이랑 경기하는지 계산해야하는데
 *     이를 코드로 구현하기가 의외로 간단하지 않다
 *
 * - enum 을 적극적으로 활용해서 실수를 줄이자
 *   + 필자의 경우 (각 팀, 경기시 팀구분, 경기결과) 모두 enum 으로 정의해서
 *     백트래킹 함수내 경기결과를 다루는 코드를 훨씬 수월하게 작성했다
 */

# Code

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


#include <iostream>
#include <vector>

using namespace std;

#define endl '\n'

// Set up : Global Variables
enum Team { A, B, C, D, E, F };
enum Side { L, R };
enum Status { WIN, DRAW, LOSE };
int P[15][2] = {
        {A, B}, {A, C}, {A, D}, {A, E}, {A, F},
        {B, C}, {B, D}, {B, E}, {B, F},
        {C, D}, {C, E}, {C, F},
        {D, E}, {D, F},
        {E, F}
};
// int call = 0; /* isLogicallyValid 함수가 호출된 횟수 */

// Set up : Functions Declaration
bool isVarsValid(vector<vector<int>> &score);
bool isLogicallyValid(int cnt, vector<vector<int>> &score);


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

    // Set up : Input
    for (int i=0; i<4; i++) {
        /* score[team][WIN] - team 이 승리한 횟수
         * score[team][DRAW] - team 이 무승부한 횟수
         * score[team][LOSE] - team 이 패배한 횟수 */
        vector<vector<int>> score(6, vector<int>(3, 0));
        for (int j=0; j<6; j++) {
            for (int k=0; k<3; k++) {
                cin >> score[j][k];
            }
        }

        // Process
        /* 먼저 각 팀의 (승리, 무승부, 패배) 한 횟수가 값적으로 유효한지 살펴보고
         * 그 다음 이 결과가 논리적으로 가능한 결과인지 알아봄 */
        bool valid = isVarsValid(score) && isLogicallyValid(0, score);

        // Control : Output
        cout << ((valid) ? 1 : 0) << ' ';
        // cout << call << endl;
    }
}

// Helper Functions
bool isVarsValid(vector<vector<int>> &score)
/* 각 팀의 (승리, 무승부, 패배) 한 횟수가 값적으로 유효하면 true 를 반환
 * 그 외 false 를 반환 */
{
    int sum = 0; /* 각 팀의 (승리, 무승부, 패배) 한 횟수의 총합 */
    for (int i=0; i<6; i++) {
        for (int j=0; j<3; j++) {
            if (score[i][j] < 0 || score[i][j] > 6) return false;
            sum += score[i][j];
        }
    }
    return sum == 30;
}

bool isLogicallyValid(int cnt, vector<vector<int>> &score)
/* 각 팀의 (승리, 무승부, 패배) 한 횟수가 논리적으로 유효하면 true 를 반환
 * 그 외 false 를 반환 */
{
    // call++; /* 함수 호출 횟수 증가 */
    /* 15번의 경기를 모두 치르면 바로 이 경우가 초기 score 로부터 가능한 결과 */
    if (cnt == 15) return true;

    int tl = P[cnt][L]; /* 왼쪽 팀 */
    int tr = P[cnt][R]; /* 오른쪽 팀 */

    /* 시나리오 1. 왼쪽 팀 승리 */
    /* 왼쪽 팀의 승리한 횟수와 오른쪽 팀의 패배한 횟수가 0 보다 크면 */
    if (score[tl][WIN] > 0 && score[tr][LOSE] > 0) {
        score[tl][WIN]--; score[tr][LOSE]--; /* 각각에서 1 씩 빼고 */
        if (isLogicallyValid(cnt+1, score)) /* 다음 경기로 넘어감 */
            return true; /* 이 시나리오가 가능할 경우
                          * 바로 true 를 반환하며 함수 종료 */ 
        score[tl][WIN]++; score[tr][LOSE]++; /* Reset */
    }

    /* 시나리오 2. 무승부 */
    /* 왼쪽 팀의 무승부한 횟수와 오른쪽 팀의 무승부한 횟수가 0 보다 크면 */
    if (score[tl][DRAW] > 0 && score[tr][DRAW] > 0) {
        score[tl][DRAW]--; score[tr][DRAW]--; /* 각각에서 1 씩 빼고 */
        if (isLogicallyValid(cnt+1, score)) /* 다음 경기로 넘어감 */
            return true; /* 이 시나리오가 가능할 경우
                          * 바로 true 를 반환하며 함수 종료 */
        score[tl][DRAW]++; score[tr][DRAW]++; /* Reset */
    }

    /* 시나리오 3. 오른쪽 팀 승리 */
    /* 왼쪽 팀의 패배한 횟수와 오른쪽 팀의 이긴 횟수가 0보다 크면 */
    if (score[tl][LOSE] > 0 && score[tr][WIN] > 0) {
        score[tl][LOSE]--; score[tr][WIN]--; /* 각각에서 1씩 빼고 */
        if (isLogicallyValid(cnt+1, score)) /* 다음 경기로 넘어감 */
            return true; /* 이 시나리오가 가능할 경우
                          * 바로 true 를 반환하며 함수 종료 */
        score[tl][LOSE]++; score[tr][WIN]++; /* Reset */
    }

    /* 모든 시나리오가 불가능 하다면 지금까지의 경기결과는 불가능한 결과 */
    return false;
}
profile
이런 미친 게임을 봤나! - 옥냥이

0개의 댓글