Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

69-9kyo-hwang #232

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

69-9kyo-hwang #232

wants to merge 5 commits into from

Conversation

9kyo-hwang
Copy link
Collaborator

@9kyo-hwang 9kyo-hwang commented Oct 8, 2024

🔗 문제 링크

2022 KAKAO BLIND RECRUITMENT 사라지는 발판

플레이어 A와 B가 게임을 진행하는데, 양 플레이어가 캐릭터를 몇 번 움직이게 될 지 예측하고자 한다.
발판이 있는 부분과 없는 부분으로 이루어진 최대 5 x 5 크기의 격자 보드에서 캐릭터를 움직인다. 최초의 캐릭터 위치는 반드시 발판 위에 있으며, 캐릭터는 상하좌우로 인접한 4칸 중 발판이 있는 곳으로만 이동한다. 밟고 있던 발판은 다른 곳으로 이동할 때 사라진다.

게임이 종료되는 조건은 아래와 같이 2가지이다.

  1. 움직일 차례인데 상하좌우 주변 4칸 모두 발판이 없거나 보드 밖이라 이동할 수 없을 때
  2. 두 캐릭터가 같은 발판 위에 있을 때, 상대 캐릭터가 다른 발판으로 이동해 자신 캐릭터가 서있던 발판이 사라졌을 때

항상 플레이어 A가 먼저 시작하며, 두 플레이어는 "최적의 플레이"를 수행한다. 최적의 플레이는 다음을 의미한다.

  • 이길 수 있는 플레이어(실수만 하지 않으면 항상 이기는 플레이어)는 최대한 빨리 승리하도록 플레이한다.
  • 질 수 밖에 없는 플레이어(최선을 다해도 상대가 실수하지 않으면 항상 질 수 밖에 없는 플레이어)는 최대한 오래 버티도록 플레이한다.

게임 보드의 초기 상태를 나타내는 2차원 정수 배열 board와 플레이어 A의 캐릭터 초기 위치를 나타내는 정수 배열 aloc, 플레이어 B의 캐릭터 초기 위치를 나타내는 정수 배열 bloc이 매개변수로 주어진다. 양 플레이어가 최적의 플레이를 했을 때, 두 캐릭터가 움직인 횟수의 합을 return 하도록 solution 함수를 완성하라.

✔️ 소요된 시간

2시간 박고 GG

✨ 수도 코드

보드 크기가 작기 때문에, 모든 경우를 다 확인하면서 최선의 경우일 때의 움직임 횟수를 반환해주면 된다. 그렇기 때문에 DFS를 사용하며, base case는 문제에서 설명한 것처럼 "더 이상 움직일 수 없는 경우"이다.

다만 각 플레이어는 이기거나 지기 위해 행동하지 않는다. 그저 "최선을 다할 뿐"이다. 다시 말해, 매 턴마다 "이기고 있다면 최대한 빨리 끝내도록, 지고 있다면 최대한 오래 버티도록" 움직임을 선택한다. 그렇기 때문에 현재 내가 "이기고 있는 지, 지고 있는 지" 확인하는 것이 중요하다.

플레이어 A를 "나", 플레이어 B를 "상대"라고 가정하자.
플레이어 A가 항상 먼저 시작한다고 했기 때문에, 끝났을 때의 턴이 짝수라면 내가 진 것이고 홀수라면 내가 이긴 것이다.

  • 플레이어가 겹치는 단순한 케이스는 다음과 같이 파악할 수 있으며, 그 외의 경우에 대해서도 시뮬레이션 해보면 위 전제는 파악할 수 있을 것이다.
    제목을-입력해주세요_ (3)

이런 식으로 승패를 구할 수 있으므로, 각 턴마다 취해야 할 행동을 승패 결과에 따라 다르게 정해줘야 한다.

  • 내가 지는 턴(짝수)인 경우 움직임 수가 가장 "많은 것"이 최선이다.
  • 내가 이기는 턴(홀수)인 경우 움직임 수가 가장 "적은 것"이 최선이다.
  • 단, 이길 수 있는 경우의 수가 하나라도 존재한다면 (지는 쪽 움직임 수) < (이기는 쪽 움직임 수)라도 취한다(이길 수 있는 플레이어는 반드시 이겨야 하므로).

이러한 조건을 가지고 코드를 작성한다.

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

const vector<pair<int, int>> Offset{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};

vector<vector<int>> Board;

bool OutOfBound(int r, int c)
{
    return r < 0 || r >= Board.size() || c < 0 || c >= Board[0].size();
}

int DFS(pair<int, int> ALoc, pair<int, int> BLoc)  // A는 나, B는 상대방
{
    const auto& [r, c] = ALoc;
    if(Board[r][c] == 0)
    {
        return 0;
    }
    
    int Answer = 0;  // 최종적으로 움직인 횟수
    Board[r][c] = 0;  // 백트래킹(다음 발판으로 이동하면 현재 발판은 사라짐)
    
    for(const auto& [dr, dc] : Offset)
    {
        int nr = r + dr, nc = c + dc;
        if(OutOfBound(nr, nc) || Board[nr][nc] == 0)
        {
            continue;
        }
        
        int RetVal = DFS(BLoc, {nr, nc}) + 1;  // 나와 상대방이 다음 턴에서 바뀜
        
        // 1. 지금까지 패배(짝수) & 이번에 승리(홀수) -> 승리로 갱신
        if(Answer % 2 == 0 && RetVal % 2 == 1) Answer = RetVal;
        // 2. 지금까지 패배(짝수) & 이번에도 패배(짝수) -> 최대한 오래 버티도록 더 큰 움직임 횟수
        else if(Answer % 2 == 0 && RetVal % 2 == 0) Answer = max(Answer, RetVal);
        // 3. 지금까지 승리(홀수) & 이번에도 승리(홀수) -> 최대한 빨리 승리하도록 더 작은 움직임 횟수
        else if(Answer % 2 == 1 && RetVal % 2 == 1) Answer = min(Answer, RetVal);
    }
    
    Board[r][c] = 1;
    return Answer;
}

int solution(vector<vector<int>> InBoard, vector<int> InALoc, vector<int> InBLoc) 
{
    Board = InBoard;
    return DFS({InALoc[0], InALoc[1]}, {InBLoc[0], InBLoc[1]});  // 최초에는 플레이어 A가 움직임
}

📚 새롭게 알게된 내용

수도 코드 자체는 심플하게 잡히는 것 같은데 이상한 데서 자꾸 터져서 블로그 찾아보니 게임 이론의 최소 극대화 알고리즘란 것이었다. 이 알고리즘을 제대로 알고 있어야 이해가 가는데, 나도 아직 이해를 다 하지 못해 더 확인해봐야 할 것 같다.

나름 여태까진 재귀 정도는 괜찮게 다룰 수 있다고 생각했는데 머리 띵...

도움 받은 블로그: BaaaaaaaarkingDog

@9kyo-hwang 9kyo-hwang self-assigned this Oct 8, 2024
@9kyo-hwang 9kyo-hwang changed the title 69 9kyo hwang 69-9kyo-hwang Oct 8, 2024
Copy link
Member

@gjsk132 gjsk132 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 A랑 B랑 이기는 경우를 2번 도는데,
이기는 쪽은 가까워지게, 지는 쪽은 멀어지게 코드를 짰는데...
생각해보니까 뭔가 예외가 되는 부분이 생겨서 포기했슴니다...
코드 보니까 왤케 짧죠... ㅜㅠ

매턴마다 이기는 쪽이랑 지는 쪽 판단해서 하는거... 어질어질하네요...ㅜㅠ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants