본문 바로가기

올림피아드 문제 풀이

IOI 2018 4번) Mechanical Doll

https://www.acmicpc.net/problem/20064

 

20064번: Mechanical Doll

Let M = 4, N = 4, and A = [1, 2, 1, 3]. The grader calls create_circuit(4, [1, 2, 1, 3]). The above figure shows a circuit, which is described by a call answer([1, -1, -2, 0, 2], [2, -2], [3, 1]). The numbers in the figure are the serial numbers of the dev

www.acmicpc.net

<Step 1> Switch의 성질 관찰

 

Switch는 다시 초기 상태로 돌아와야 하기 때문에, 공이 짝수 번 들어와야 합니다.

Switch에 공이 2a번 들어왔다면, X로 a번, Y로 a번 공이 나갑니다.

 

<Step 2> Switch를 이용한 작은 기계 만들어보기

 

문제를 풀기 위해 큰 기계를 만들어보기 전에, 공이 들어왔을 때 순서대로 1, 2, .., n번 trigger로 이동하는 기계를 만들어봅시다.

 

n = 2일 때 : 

n = 3일 때 :

N = 3일 때 -1번 Switch에 들어오는 공의 개수에 주목해봅시다. 3개의 공이 들어오고 -2번 Switch에서 1개가 돌려보내져서 총 4개의 공이 들어옵니다.

 

-2번, -3번 Switch는 각각 2개의 공이 들어오고, 각 방향으로 1개의 공을 내보냅니다.

 

<Step 3> Switch를 이용한 큰 기계 설계하기

 

공이 i번째로 들어올 때, A[i-1]번으로 가게 하는 기계를 설계해봅시다. 기계에 공이 N번 들어올 때 원하는 위치로 보내줘야 합니다.

 

N이 2의 거듭제곱일 때는 자명합니다. 공이 들어오는 순서대로 1, 2, 3, ... 단계 Switch라고 할 때, 1단계 Switch의 출구를 모두 2단계 Switch, 2단계 Switch의 출구를 모두 3단계 Switch로 정하는 걸 반복해주면 됩니다.

 

N이 2의 거듭제곱이 아닐 때는 어떻게 할까요? 1단계 Switch에 들어오는 공을 억지로 2의 거듭제곱으로 만들어야 합니다. 2, 3, ... 단계 Switch에는 1단계 Switch의 1/2, 1/4, ....만큼의 공이 들어오는데, 그럼 적절하게 역으로 1단계 Switch로 보내주면 됩니다. 비트마스킹을 이용해 N을 이진수로 표현하고, 보내줘야 할 Switch를 적절히 선택하면 최대 N + log N개의 Switch로 기계를 설계할 수 있습니다.

 

<Step 4> 기계 Simulate해보기

 

기계를 완성한다고 해도, 기계에 들어온 공이 어느 곳으로 나갈지 구하기 쉽지 않습니다. 그래서, 그냥 직접 공을 굴려봅니다. 들어가는 순서대로 A[0], A[1], ...로 나가는 출구를 배정해주면 됩니다.

 

<소스 코드>

#include "doll.h"
#include <queue>
#include <stdio.h>

int x[400002], y[400002];

void change(int M, std::vector<int> &C, std::vector<int> &X, std::vector<int> &Y, std::vector<int> A){
    std::queue<int> q;
    A.push_back(0);
    int L = (int)A.size();

//trigger에 공이 들어가면, 모두 -1번 Switch로 보낸다.
    for(int i=0;i<=M;i++) C[i] = -1;

    for(int i=0;i<=400000;i++){
        x[i] = 1;
        y[i] = 1;
    }

    int h = 0;
    while((1<<h) < L) h++;
    L = (1<<h) - L;

    q.push(1);

    int cnt = 1;

//기계를 설계한다.
    for(int i=h-1;i>=0;i--){
        std::queue<int> temp;
        int now = q.front(); q.pop();
        if(i == 0 && (1&L)){
            x[now] = -1;
        }
        if(i == 0) break;

        if((1<<i) & L){
            x[now] = -1;
            ++cnt; y[now] = -cnt; temp.push(cnt);
        }
        else{
            ++cnt; x[now] = -cnt; temp.push(cnt);
            ++cnt; y[now] = -cnt; temp.push(cnt);
        }
        while(!q.empty()){
            int now = q.front(); q.pop();
            ++cnt; x[now] = -cnt; temp.push(cnt);
            ++cnt; y[now] = -cnt; temp.push(cnt);
        }
        while(!temp.empty()){
            int now = temp.front(); temp.pop();
            q.push(now);
        }
    }

    std::vector<bool> visited(cnt);
    X.resize(cnt); Y.resize(cnt);

    for(int i=0;i<cnt;i++){
        X[i] = x[i+1];
        Y[i] = y[i+1];
    }

//설계한 기계에 공을 흘려보낸다.
    for(int i=0;i<(1<<h) - L;i++){
        int curr = -1;
        for(;;){
            if(!visited[-curr-1]){
                visited[-curr-1] = true;
                if(x[-curr] == 1) { X[-curr-1] = A[i]; break; }
                else curr = x[-curr];
            }
            else{
                visited[-curr-1] = false;
                if(y[-curr] == 1) { Y[-curr-1] = A[i]; break; }
                else curr = y[-curr];
            }
        }
    }
}

void create_circuit(int M, std::vector<int> A) {
    std::vector<int> C(M + 1);
    std::vector<int> X, Y;
    change(M, C, X, Y, A);
    answer(C, X, Y);
}

'올림피아드 문제 풀이' 카테고리의 다른 글

IOI 2018 5번 Highway Tolls 풀이  (0) 2021.06.24
2021.01.03 PS - CEOI 2017 2번  (0) 2021.01.04