본문 바로가기
알고리즘 문제 풀이/백준

백준 16986 인싸들의 가위바위보 Kotlin (완전 탐색)

by 옹구스투스 2022. 9. 17.
반응형

문제 출처 : https://www.acmicpc.net/problem/16986

 

16986번: 인싸들의 가위바위보

두 사람이 같은 손동작을 내어 무승부가 발생할 경우 경기 진행 순서상 뒤인 사람이 이긴 것으로 간주함에 다시 한 번 유의한다. 구체적으로, 경기 진행 순서는 지우, 경희, 민호 순으로 고정되

www.acmicpc.net

문제

혹시 마지막으로 엠티를 간 것이 언제인가? 엠티를 안간지 꽤 오래됐다면 요즘 유행하는 인싸들의 가위바위보를 모를 것이다. 요즘 인싸들은 엠티에서 평범한 가위바위보를 시시하다는 이유로 더 이상 취급하지 않는다. 대신 가위불바위총번개악마용물공기보스펀지늑대나무사람뱀을 한다. 이 게임의 명칭이 다소 긴 관계로 문제 내에서는 전체 명칭을 적는 대신 이 게임의 또 다른 이름인 인싸 가위바위보로 부르겠다. 인싸 가위바위보는 평범한 가위바위보와 같이 각 손동작간의 상성이 정해져있다. 

인싸 가위바위보는 평범한 가위바위보보다 흥미진진하고 재밌지만 3명 이상이 경기를 할 때 누가 이기고 누가 졌는지를 빠르게 알기 힘들다는 단점이 있다. 그렇기에 3명 이상의 사람들 사이에서 인싸 가위바위보를 할 때는 모두가 동시에 경기를 진행하는 대신 일대일 경기를 여러 번 진행해 누가 우승했는지 판단한다. 3명이서 인싸 가위바위보를 할 때의 우승자를 정하기 위한 구체적인 방식은 아래와 같다. 편의상 참가자 3명을 A, B, C라고 하자.

  1. A, B, C는 게임 시작 전 우승을 위해 필요한 승수와 경기 진행 순서를 미리 합의한다. 경기 진행 순서가 A, B, C라고 가정하자.
  2. 먼저 A와 B가 경기를 진행해 승자를 결정한다. 만약 두 사람이 같은 손동작을 내어 무승부가 발생할 경우 경기 진행 순서상 뒤인 사람이 이긴 것으로 간주한다. 즉 A와 B가 같은 손동작을 내면 B의 승리, A와 C가 같은 손동작을 내면 C의 승리, B와 C가 같은 손동작을 내면 C의 승리이다. 
  3. 이전 경기의 승자와 이전 경기에 참여하지 않은 사람이 경기를 진행해 승자를 결정한다.
  4. 특정 사람이 미리 합의된 승수를 달성할 때 까지 3을 반복한다.
  5. 합의된 승수를 최초로 달성한 사람이 우승한다.

밑의 표는 침, 펄, 풍 세 사람이 인싸 가위바위보를 진행하는 예시이다. 우승을 위해 필요한 승수는 3승이고 침, 펄, 풍 순서로 경기를 진행한다.

인싸 가위바위보 결과 풍이 제일 먼저 3승에 도달했으므로 우승자는 풍이다. 1라운드, 3라운드에서 두 사람이 같은 손동작을 냈을 때 경기 진행 순서상 뒤인 사람이 승리하는 것을 확인할 수 있다.

컴퓨터공학과 새내기 지우는 첫 엠티에서 친구 경희, 민호와 인싸 가위바위보를 할 생각에 굉장히 신나있다. 지우는 경희와 민호의 행동 패턴을 빅데이터로 분석해 인싸 가위바위보를 하는 중 경희와 민호의 차례가 왔을 때 이들이 낼 손동작의 순서를 정확히 알고 있다. 그래서 마음만 먹으면 전승 우승이 가능하지만 경기를 흥미진진하게 이끌기 위해 인싸 가위바위보를 할 때 모든 손동작을 다르게 내고 싶어한다. 지우의 즐거운 대학생활을 위해 인싸 가위바위보의 상성표와 경희, 민호가 낼 손동작의 순서가 주어졌을 때 지우가 모든 손동작을 다르게 내어 우승할 수 있는지 판단하는 프로그램을 만들자. 경기 진행 순서는 지우, 경희, 민호 순으로 정해져있다.

입력

첫째 줄에 인싸 가위바위보의 손동작 수를 나타내는 N(1 ≤ N ≤ 9)과 우승을 위해 필요한 승수 K(1 ≤ K ≤ 6)가 한 칸의 빈칸을 사이에 두고 주어진다.

그 다음 N개의 줄에 대해 상성에 대한 정보 Ai,j가 주어진다. i+1번째 줄에는 N개의 정수 Ai,1, Ai,2, Ai,3, ..., Ai,N이 한 칸의 빈칸을 사이에 두고 주어진다. Ai,j가 2일 경우에는 i번 손동작이 j번 손동작을 이긴다는 의미이고, 1일 경우에는 비긴다는 의미이고, 0일 경우에는 진다는 의미이다. Ai,i = 1이고, i ≠ j일 때 Ai,j ≠ 1임이 보장된다. 또한 Ai,j가 2일 경우에는 Aj,i가 0이고, Ai,j가 0일 경우에는 Aj,i가 2임이 보장된다.

그 다음 줄에는 경희가 앞으로 자신이 참여하는 20경기에서 낼 손동작의 순서가 한 칸의 빈칸을 사이에 두고 주어진다. 손동작의 번호는 1 이상 N 이하이다.

그 다음 줄에는 민호가 앞으로 자신이 참여하는 20경기에서 낼 손동작의 순서가 한 칸의 빈칸을 사이에 두고 주어진다. 마찬가지로 손동작의 번호는 1 이상 N 이하이다.

출력

첫째 줄에 지우, 경희, 민호 순으로 경기를 진행할 때 지우가 모든 손동작을 다르게 내어 우승할 수 있으면 1을, 그렇지 않으면 0을 출력한다.

노트

두 사람이 같은 손동작을 내어 무승부가 발생할 경우 경기 진행 순서상 뒤인 사람이 이긴 것으로 간주함에 다시 한 번 유의한다. 구체적으로, 경기 진행 순서는 지우, 경희, 민호 순으로 고정되어있기 때문에 이전 라운드의 결과와 무관하게 지우와 경희가 같은 손동작을 냈으면 경희의 승리이고, 지우와 민호가 같은 손동작을 냈으면 민호의 승리이고, 경희와 민호가 같은 손동작을 냈으면 민호의 승리이다.

비둘기집의 원리에 의해 3(K-1)+1번의 경기를 치르면 누군가는 K번 이상 승리해 우승자가 결정되기 때문에 경희, 민호 각 사람에 대해 앞으로 20경기에서 낼 손동작의 순서만 알고 있으면 지우가 모든 손동작을 다르게 내어 우승할 수 있는지를 판단할 수 있다.

만약 지우가 가능한 모든 손동작을 한 번씩 다 낸 후에도 아직 우승자가 결정되지 않아 지우가 경기를 더 하게 된다면, 지우는 이전에 냈던 손동작을 다시 내야하므로 답은 0이 된다.

알고리즘 분류

풀이

가위바위보 그림 때문에 풀기 싫었던 문제이다,

우선 문제를 읽고 한 번 정리하는 게 필요해 보인다.

1. 가위바위보 순서는 정해져 있다. (지우, 경희, 민호)

2. 비긴 경우 (같은 걸 낸 경우) 위에서 정해진 순서가 앞선 사람이 이긴 것으로 판정한다.

3. 주어진 경희와 민호의 20개 순서는, 턴마다 절대적인 순서가 아니라 만약 얘네가 20판을 했을 때 낼 손동작의 순서이다.

4. 지우는 중복된 손동작을 내면 안 되기 때문에 모든 손동작을 한 번씩 내서 k번 이상 이겨야 한다.

5. 즉 지우가 n번 게임을 해 보면 답을 알 수 있다.

6. 첫 판은 무조건 지우 vs 경희이고, 다음부터는 이긴 사람 vs 방금 한 사람과 이긴 사람을 제외한 나머지 사람

 

위의 조건에서 3번이 함정이다.

경희 : 1 2 3 3 2

민호 : 3 1 2 1 3

만약 경희와 민호가 위처럼 주어졌을 때, 

턴 1 : 지우 vs 경희,

턴 2 : 경희 vs 민호,

턴 3 : 민호 vs 지우

경희는 1을 내고, 2를 낸다.

민호는 첫 경기(턴 2) 때 무엇을 내고 두 번째 경기(턴 3) 때 무엇을 낼까?

본인은 민호가 1을 내고 2를 낸다고 생각했었는데, 3번 조건처럼 그냥 순서의 나열이기 때문에 민호는 사실 3과 1을 낸다.

따라서 민호와 경희가 무엇을 낼지는 턴만 알고 있으면 된다고 생각했는데, 경희, 민호 각각 순서(인덱스)를 가지고 있어야 함을 알았다.

 

위의 조건들을 이용한 풀이는 다음과 같다.

지우는 n 개의 손동작을 모두 내서 k번 이겨야 하고, 이는 내는 순서에 따라 결과가 달라질 수 있다.

즉, 지우가 낼 손동작들의 순서를  n개의 순열로 구한다.

이제 준비가 끝났다.

지우가 낼 n개의 순서와, 경희, 민호가 낼 순서가 20개로 넉넉히 준비되어있다.

이제 실제로 시뮬레이션을 돌려보면 된다.

 

fun play(
    deck: Array<IntArray>,
    winner: Int,
    other: Int,
    winCnt: IntArray,
    idxArr: IntArray
)

deck : 지우, 경희, 민호가 낼 손동작 순서 집합

winner: 이긴 사람

other: 다른 사람

winCnt: 지우, 경희, 민호 각각 이긴 수

idxArr: 지우, 경희, 민호 각각 현재 낼 손동작의 순서

 

위의 매개변수를 갖는 play 함수의 body를 구현하면 끝.

지우가 k번 이상 이긴 경우 answer = 1을 하고 다른 루틴들을 모두 종료시키면 된다.

추가로 지우의 손동작 순서가 n 이상인 경우 더 해봐야 중복되는 손동작을 낼 거기 때문에 종료,

경희, 민호가 k번 이상 이겨버린 경우도 종료하면 된다.

코드

val br = System.`in`.bufferedReader()
fun getIntList() = br.readLine().trim().split(' ').map { it.toInt() }
fun getInt() = br.readLine().trim().toInt()

//결과 같을 땐 침펄풍 인덱스 큰 사람이 승
var n = 0
var k = 0
lateinit var genealogy: Array<List<Int>>
var answer = 0

fun play(
    deck: Array<IntArray>,
    winner: Int,
    other: Int,
    winCnt: IntArray,
    idxArr: IntArray
) {
    if(winner == other){
        play(deck, winner, (other+1)%3, winCnt, idxArr)
        return
    }
    if (idxArr[0] >= n ) return
    //지우 경기
    if (winner == 0 || other == 0) {
        val otherTemp = if (winner == 0) other else winner
        //지우가 이긴 경우
        if (genealogy[deck[0][idxArr[0]]][deck[otherTemp][idxArr[otherTemp]] - 1] == 2) {
            winCnt[0]++
            idxArr[0]++
            idxArr[otherTemp]++
            if(winCnt[0]>=k){
                answer = 1
                return
            }
            play(deck, 0, (otherTemp+1)%3, winCnt, idxArr)
        } else { //지우가 진 경우
            winCnt[otherTemp]++
            idxArr[otherTemp]++
            idxArr[0]++
            if(winCnt[otherTemp]<k){
                play(deck, otherTemp, 1, winCnt, idxArr)
            }
        }
    }
    //경희, 민호 경기
    else {
        var win = -1
        var lose = -1
        when (genealogy[deck[winner][idxArr[winner]] - 1][deck[other][idxArr[other]] - 1]) {
            2 -> {
                win = winner
                lose = other
            }
            0 -> {
                win = other
                lose = winner
            }
            else -> {
                //비긴 경우 순서에 의해 승패 판정
                win = if (winner < other) other else winner
                lose = if (winner < other) winner else other
            }
        }
        winCnt[win]++
        idxArr[win]++
        idxArr[lose]++
        if (winCnt[win] < k) {
            play(deck, win, (lose + 1) % 3, winCnt, idxArr)
        }
    }
}

fun permutation(deck: Array<IntArray>, visited: BooleanArray, len: Int) {
    if(answer == 1) return
    if (len == n) {
        play(deck, 0, 1, IntArray(3), IntArray(3))
        return
    }

    for (i in 0 until n) {
        if (visited[i]) continue
        visited[i] = true
        deck[0][len] = i
        permutation(deck, visited, len + 1)
        visited[i] = false
    }
}

fun main() = with(System.out.bufferedWriter()) {
    //input
    getIntList().apply {
        n = this[0]
        k = this[1]
    }
    genealogy = Array(n) { getIntList() }
    val deck = arrayOf(
        IntArray(20) { -1 },
        getIntList().toIntArray(),
        getIntList().toIntArray()
    )
    permutation(deck, BooleanArray(n), 0)
    write("$answer")
    close()
}
반응형

댓글