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

30-9kyo-hwang #115

Merged
merged 1 commit into from
Mar 6, 2024
Merged

30-9kyo-hwang #115

merged 1 commit into from
Mar 6, 2024

Conversation

9kyo-hwang
Copy link
Collaborator

@9kyo-hwang 9kyo-hwang commented Feb 26, 2024

🔗 문제 링크

23296 엘리베이터 조작

✔️ 소요된 시간

약 3시간

✨ 수도 코드

1. 문제

1명만 태울 수 있는 엘리베이터가 있다. 이 엘리베이터는 관리실에서만 조작 가능하며, 1층에서 출발한다.
image
위와 같이 한 층 당 한 명씩 기다리고 있을 때, 사람들을 모두 원하는 층에 내려주기 위해 버튼을 눌러야 하는 최소 횟수와 눌러야하는 버튼 순서를 출력하라.

2. 들어가기에 앞서

알고리드미 디코 지박령 @pknujsp @tgyuuAn 이 막 얘기하고 있길래 뭔가 하고 봤는데�
image
�나도 당해버렸다.

image

위상정렬 문제라길래 그거를 고려해서 코드를 막 짰었는데, 솔직히 위상정렬 아닌 것 같다. 사실상 그리디 문제라고 생각된다.

3. 풀이

원하는 층에 내려주기 위해 눌러야 하는 버튼 횟수를 최소로 만드는 것이 목적이다. 위의 예시를 다시 한 번 확인해보자.

1층 사람은 4층, 2층 사람은 4층, 3층 사람은 1층, 4층 사람은 5층, 5층 사람은 2층을 가야 한다.
만약 1층부터 각 사람마다 원하는 층을 바로바로 데려다 준다고 하면, 버튼은 4 - 2 - 4 - 3 - 1 - 4 - 5 - 2 이렇게 눌러져야 할 것이다.

  • 1층에서 출발하는데, 1층 사람은 4층을 가야하므로 4룰 눌러 4층으로 보냄. 그 후 2층으로 가기 위해 다시 2를 누름
  • 2층 사람은 4층으로 가야하므로 4를 눌러 4층으로 보냄. 그 후 3층으로 가기 위해 3을 누름
  • 3층 사람은 1층으로 가야하므로 1를 눌러 1층으로 보냄. 그 후 4창으로 가기 위해 4를 누름
  • 4층 사람은 5층으로 가야하므로 5를 눌러 5층으로 보냄. 바로 5층이므로 5층 사람을 태움
  • 5층 사람은 2층으로 가야하므로 2를 눌러 2층으로 보냄. 끝

반면 사람을 내리자마자 그 층의 사람을 다시 태워 바로 보내버리는, 줄줄이 이어서 태우고 내리고 태우고 내리고를 반복한다고 해보자. 아래와 같은 순서라면 4 - 5 - 2 - 4 - 3 - 1이다.

  • 1층에서 출발하는데, 1층 사람은 4층으로 가야하므로 4를 누름
  • 바로 4층에 있는 사람을 태우는데, 5층으로 가야하므로 5를 누름
  • 바로 5층 사람을 태움, 2층으로 가야하므로 2를 누름
  • 2층 사람 태움, 4층 가야하므로 4를 누름
  • 4층에선 태울 사람이 없으므로, 마지막으로 3층을 감
  • 3층 사람을 태워 1층으로 보내기 위해 1을 누름

버튼을 눌러야 하는 횟수가 6번으로 줄어든다. 이런 식으로 줄줄이 물어서 옮기는 것이 버튼을 누르는 횟수를 줄일 수 있다. 이를 코드로 구현해보자.

    auto Elevation = [&](int Floor = 1)
    {
        while(!IsOutOfHere[Floor])
        {
            IsOutOfHere[Floor] = true;
            int NextFloor = A[Floor];
            
            if(NumOfComeHere[NextFloor] > 0)
            {
                NumOfComeHere[NextFloor] -= 1;
                BtnsToPress.emplace_back(NextFloor);
                Floor = NextFloor;
            }
        }
    };
    
    Elevation();

엘리베이터 이동을 담당하는 함수이다. IsOutOfHere[Floor]는 "Floor층에 있는 사람이 나갔는가"를 의미한다. 각 층에는 한 사람이 존재하므로, 한 번 true로 바뀌면 더 이상 Floor 층에서 다른 층으로 이동할 수 없기 때문에 이런 조건이 걸려있다.
나갈 사람이 있다면 반복문 안에 들어오면서, true 처리를 해주고 다음 층 A[Floor]를 판단한다.
NumOfComeHere[NextFloor]는 NextFloor 층에 가고자 하는 사람 수를 의미한다. 만약 이 수가 0이라면 Floor 층의 사람도 이미 이전 엘리베이터 이동에 의해 갔음을 의미하므로 다음 층으로 이동하지 않아야 한다.
다음 층으로 가지 않았다면 문제에서 찾고자 하는 버튼이므로 기록하고, 현재 층을 다음 층으로 갱신해준다.

최초의 엘리베이터는 1층에 있으므로, 1층에서 시작하는 함수를 한 번 호출한다.

예시를 그래프로 표현해서 1층에 대해 Elevation 함수를 수행하면 다음과 같이 진행된다.
제목을-입력해주세요_ (1)

1층에서 한 사이클을 수행하고 나면, 위 그림처럼 꼬리물기에 포함되지 않은 층이 있을 수 있다. 이런 층들은 1. 나갈 사람만 있던가 2. 나갈 사람도 있고 들어와야 할 사람도 있거나 3. 들어와야 할 사람만 있거나 셋 중 하나일 것이다.

위 설명에서 짐작챘을 것인데, 결국 같은 원리로 나갈 사람만 있는 층으로 이동해서 꼬리물기를 시전해버리는 게 버튼 누르는 횟수가 최소가 된다.

    for(int Floor = 1; Floor <= N; ++Floor)
    {
        if(IsOutOfHere[Floor])
        {
            continue;
        }
        
        if(NumOfComeHere[Floor] == 0)
        {
            BtnsToPress.emplace_back(Floor);
            Elevation(Floor);
        }
    }

그래서 모든 층을 쭉 훑어보면서, Floor 층에서 안나간 사람이 있으며 더 이상 Floor 층에 들어올 사람이 없는 층을 우선적으로 Elevation 함수를 수행한다.
여기서는 1층에서 출발하는 것이 아니기 때문에, 지금 Floor로 이동을 해와야 해서 Floor 층도 기록을 해줘야 한다.

이 부분은 약간 의문인게, 이렇게 순회를 하고도 아직 방문하지 않은 층이 남아있을 수 있다고 한다. �엣지 케이스를 찾아보고 싶은데, 떠오르지가 않는다.
암튼 아직 순회하지 않은 케이스를 처리하기 위해 한 번 더 순회한다.

    for(int Floor = 1; Floor <= N; ++Floor)
    {
        if(IsOutOfHere[Floor])
        {
            continue;
        }
        
        BtnsToPress.emplace_back(Floor);
        Elevation(Floor);
    }

마지막으로 누른 버튼 횟수와 버튼 종류를 출력하면 완성이다.

    cout << BtnsToPress.size() << "\n";
    for(const int& Btn : BtnsToPress)
    {
        cout << Btn << " ";
    }

전체 코드

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    cin.tie(nullptr)->sync_with_stdio(false);
    
    int N; cin >> N;
    
    vector<int> A(N + 1, 0), NumOfComeHere(N + 1, 0);
    vector<bool> IsOutOfHere(N + 1, false);
    
    for(int i = 1; i <= N; ++i)
    {
        cin >> A[i];
        NumOfComeHere[A[i]] += 1;
    }
    
    vector<int> BtnsToPress;
    auto Elevation = [&](int Floor = 1)
    {
        while(!IsOutOfHere[Floor])
        {
            IsOutOfHere[Floor] = true;
            int NextFloor = A[Floor];
            
            if(NumOfComeHere[NextFloor] > 0)
            {
                NumOfComeHere[NextFloor] -= 1;
                BtnsToPress.emplace_back(NextFloor);
                Floor = NextFloor;
            }
        }
    };
    
    Elevation();
    
    for(int Floor = 1; Floor <= N; ++Floor)
    {
        if(IsOutOfHere[Floor])
        {
            continue;
        }
        
        if(NumOfComeHere[Floor] == 0)
        {
            BtnsToPress.emplace_back(Floor);
            Elevation(Floor);
        }
    }
    
    for(int Floor = 1; Floor <= N; ++Floor)
    {
        if(IsOutOfHere[Floor])
        {
            continue;
        }
        
        BtnsToPress.emplace_back(Floor);
        Elevation(Floor);
    }
    
    cout << BtnsToPress.size() << "\n";
    for(const int& Btn : BtnsToPress)
    {
        cout << Btn << " ";
    }

    return 0;
}

📚 새롭게 알게된 내용

왜 위상정렬 카테고리가 붙었는 지 모르겠다. 진입차수 개념이 사용돼서 그런가?
그리고 마지막에 한 번 더 모든 층을 확인해야 하는 이유가 무엇인 지 명확히 모르겠다. 엣지 케이스 하나만 잡아서 설명해주실 분...?

Copy link
Collaborator

@Dolchae Dolchae left a comment

Choose a reason for hiding this comment

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

예제 보고 이해하는 것도 바로바로 안되는데 코드로 구현까지 하시는 거 진짜 엄청난 것 같아요... 수고하셨습니다😊

@gjsk132
Copy link
Member

gjsk132 commented Feb 29, 2024

뭐얔ㅌㅋㅌㅋㅌ 그림보고 한참 웃었어요ㅋㅌㅋㅌㅋㅌ

Copy link
Member

@xxubin04 xxubin04 left a comment

Choose a reason for hiding this comment

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

엄청 어려운 문제인 것 같은데 자세하게 설명해주셔서 이해하면서 볼 수 있었습니다!
중간에 있는 그림도 너무 웃겼네요ㅋㅋㅋㅋㅋㅋㅋㅋㅋ😁
엣지 케이스라는 것도 있군요?
찾아보니 극한의 상황에서 왜곡되는 현상이라고 하는데 알고리즘 쉽지 않네요...🤔

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.

꼬리잡기를 사용해야 하고, 제일 끝에 있는 사람을 우선적으로 선택하는 것이 최소로 버튼을 누르는 방법이겠구나! 는 금방 알아챘는데... 이걸 어떻게 구현해야하지?에서 2일을 태웠습니다. 유니온 파인드인가..? 네... 하루를 날렸구요. 그냥 그래프로 해볼까 하다가 그래프로도 이래저래 하다가 결국엔 포기했습니다. 😢

설명보고나서야 저런 방법이라는 생각이 드네요...

엣지 케이스에 대해서 생각해봤는데...

3층 사람이 5층을 가고싶고, 5층 사람이 3층에 가고 싶은 사람이 있다면, 나갈 사람만 있는 층이 아니니까... 독립된 사이클이면 탐색이 안되지 않을까?! 라는 추측만 하고 있습니다...

리뷰 늦어서 죄송합니다😭 다음 PR 리뷰도 이어서 호다닥 하겠습니당...

@9kyo-hwang 9kyo-hwang merged commit d083136 into main Mar 6, 2024
8 checks passed
@9kyo-hwang 9kyo-hwang deleted the 30-9kyo-hwang branch March 6, 2024 03:52
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.

4 participants