민규의 흔적

[JavaScript 자바스크립트] 백준 15662번 - 톱니바퀴(2) 본문

BOJ

[JavaScript 자바스크립트] 백준 15662번 - 톱니바퀴(2)

민규링 2024. 7. 4. 21:29

2024년 7월 4일

문제 링크 : 백준 15662번 - 톱니바퀴(2)

문제

 
 
 

총 8개의 톱니를 가지고 있는 톱니바퀴 T개가 아래 그림과 같이 일렬로 놓여져 있다. 또, 톱니는 N극 또는 S극 중 하나를 나타내고 있다. 톱니바퀴에는 번호가 매겨져 있는데, 가장 왼쪽 톱니바퀴가 1번, 그 오른쪽은 2번, ..., 가장 오른쪽 톱니바퀴는 T번이다. 아래 그림은 T가 4인 경우이다.

이때, 톱니바퀴를 총 K번 회전시키려고 한다. 톱니바퀴의 회전은 한 칸을 기준으로 한다. 회전은 시계 방향과 반시계 방향이 있고, 아래 그림과 같이 회전한다.

톱니바퀴를 회전시키려면, 회전시킬 톱니바퀴와 회전시킬 방향을 결정해야 한다. 톱니바퀴가 회전할 때, 서로 맞닿은 극에 따라서 옆에 있는 톱니바퀴를 회전시킬 수도 있고, 회전시키지 않을 수도 있다. 톱니바퀴 A를 회전할 때, 그 옆에 있는 톱니바퀴 B와 서로 맞닿은 톱니의 극이 다르다면, B는 A가 회전한 방향과 반대방향으로 회전하게 된다. 예를 들어, 아래와 같은 경우를 살펴보자.

두 톱니바퀴의 맞닿은 부분은 초록색 점선으로 묶여있는 부분이다. 여기서, 3번 톱니바퀴를 반시계 방향으로 회전했다면, 4번 톱니바퀴는 시계 방향으로 회전하게 된다. 2번 톱니바퀴는 맞닿은 부분이 S극으로 서로 같기 때문에, 회전하지 않게 되고, 1번 톱니바퀴는 2번이 회전하지 않았기 때문에, 회전하지 않게 된다. 따라서, 아래 그림과 같은 모양을 만들게 된다.

위와 같은 상태에서 1번 톱니바퀴를 시계 방향으로 회전시키면, 2번 톱니바퀴가 반시계 방향으로 회전하게 되고, 2번이 회전하기 때문에, 3번도 동시에 시계 방향으로 회전하게 된다. 4번은 3번이 회전하지만, 맞닿은 극이 같기 때문에 회전하지 않는다. 따라서, 아래와 같은 상태가 된다.

톱니바퀴 T개의 초기 상태와 톱니바퀴를 회전시킨 방법이 주어졌을 때, 최종 톱니바퀴의 상태를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 톱니바퀴의 개수 T (1 ≤ T ≤ 1,000)가 주어진다. 

둘째 줄부터 T개의 줄에 톱니바퀴의 상태가 가장 왼쪽 톱니바퀴부터 순서대로 주어진다. 상태는 8개의 정수로 이루어져 있고, 12시방향부터 시계방향 순서대로 주어진다. N극은 0, S극은 1로 나타나있다.

다음 줄에는 회전 횟수 K(1 ≤ K ≤ 1,000)가 주어진다. 다음 K개 줄에는 회전시킨 방법이 순서대로 주어진다. 각 방법은 두 개의 정수로 이루어져 있고, 첫 번째 정수는 회전시킨 톱니바퀴의 번호, 두 번째 정수는 방향이다. 방향이 1인 경우는 시계 방향이고, -1인 경우는 반시계 방향이다.

출력

총 K번 회전시킨 이후에 12시방향이 S극인 톱니바퀴의 개수를 출력한다.

알고리즘 분류

  • 구현
  • 시뮬레이션

 

문제 접근

 

문제가 정말 긴 구현 문제이다. 문제가 길고 복잡해보이는 만큼 핵심을 잘 캐치해내야 접근이 수월해진다.

 

문제 핵심

1. 톱니바퀴는 각각 8개의 톱니를 가지고 있으며, 12시 톱니부터 시계방향으로 차례로 주어진다. 또한 각 톱니는 N극 또는 S극을 지니고 있으며, 0은 N극 1은 S극을 의미한다.

2. 특정 톱니바퀴가 회전하면 맞닿은 톱니바퀴가 영향을 받을 수 있다. 영향을 받는 조건은 맞물려있는 톱니가 서로 반대 자성을 띄고 있는 경우이다.

3. 여기서 맞닿은 톱니란, 회전하는 톱니바퀴의 3시 방향(2번 인덱스) 톱니와 오른쪽 톱니바퀴의 9시 방향(6번 인덱스) 톱니가 맞닿은 톱니이며 회전하는 톱니바퀴의 9시 방향(6번 인덱스) 톱니와 왼쪽 톱니바퀴의 3시 방향(2번 인덱스) 톱니가 맞닿은 톱니이다.

4. 특정 톱니바퀴가 특정 방향으로 돌아간다면 영향을 받은 톱니바퀴는 해당 방향의 반대 방향으로 돌아간다. 


5. ★중요★ 모든 톱니바퀴는 동시에 돌아간다. 한 톱니바퀴와 영향을 받는 톱니바퀴들이 돌아간 이후, 영향을 받는 톱니바퀴들에게 영향을 받은 톱니바퀴가 재귀적으로 돌아가는 것이 아닌 한 번에 돌아간다는 의미이다.

 

 

모든 톱니바퀴는 12시 방향부터 차례로 8개의 톱니의 자성이 기록되어 있다.

예를 들어 1001100의 톱니바퀴가 존재한다면 0 번째 인덱스는 12시 방향이며 2 번째 인덱스는 3시 방향, 4번째 인덱스는 6시 방향, 6 번째 인덱스는 9시 방향이라는 의미이다.

 

또한, 특정 톱니바퀴가 시계 방향으로 돌 예정이고 맞닿은 톱니바퀴의 톱니가 서로 반대 자성이라면 맞닿은 톱니바퀴는 무조건 시계 반대 방향으로 돈다. 맞닿은 톱니바퀴가 영향을 받아 회전하게 될 경우, 영향을 주는 톱니바퀴의 회전 방향의 반대 방향으로 회전한다는 의미이다.

 

가장 실수하기 쉬운 부분은 5번 핵심 사항이다.

최초로 회전하는 톱니바퀴와 맞닿은 톱니바퀴의 톱니가 자성이 반대라면 해당 톱니바퀴도 회전하게 될 것이다.

그렇다고, 회전시킬 수 있는 톱니바퀴를 회전시킨 이후 상태를 기준으로 그 다음 맞닿은 다른 톱니바퀴가 회전할 수 있는지 확인하는 식으로 순차적으로 수행하면 안 된다는 점이다.

 

모든 톱니바퀴는 동시에 회전한다. 이 점이 가장 중요하다.

 

그렇기에, 회전하는 톱니바퀴를 기준으로 왼쪽과 오른쪽을 각각 탐색하여 각각 이번 턴에 회전할 수 있는지, 회전한다면 어느 방향인지를 배열에 저장하여 기억해놓고 모든 톱니바퀴에 대한 탐색이 끝났다면 모든 톱니바퀴를 재탐색하며 동시에 정해진 방향으로 회전시켜줘야 한다.


입력 예제

 

4
10101111
01111101
11001110
00000010
2
3 -1
1 1

 

 

 

출력 예제

 

3

 

 


 

주의할 점

 

문제 접근에서 문제 핵심 5가지 중, 5번째 핵심 사항이 가장 실수하기 쉬운 부분이다.

모든 톱니는 한 번에 돌아간다는 점을 숙지하고 한 사이클에 회전하게 될 모든 상태를 배열에 기억해두었다가 한 번에 회전시켜야 한다.

 


전체 코드

 

// 15662

const file = process.platform === "linux" ? "dev/stdin" : "./input.txt";
const input = require('fs').readFileSync(file).toString().trim().split("\n");

// 데이터 스플릿
const T = parseInt(input[0].trim());

// 인덱스 편하게 접근하기 위해 0번 인덱스는 더미
var gear = [[-1]];
// N극은 0, S극은 1
// 12시 방향부터 시계방향대로 극이 주어짐
for (let i = 1; i <= T; i++) {
    gear.push(input[i].trim().split('').map(Number));
}

const K = parseInt(input[T + 1]);

var lotate = [];
for (let i = T + 2; i < input.length; i++) {
    lotate.push(input[i].trim().split(' ').map(Number));
}

// 기어가 시계 혹은 반시계로 계속 회전하게되니 큐 자료구조를 활용
/**
 * 회전한 기어의 2번 인덱스 < -- > 오른쪽 기어의 6번 인덱스 
 * 회전한 기어의 6번 인덱스 < -- > 왼쪽 기어의 2번 인덱스
 */
for (let idx = 0; idx < K; idx++) {
    const [now_gear, lotat_direct] = lotate[idx];

    var gears_lotate = Array.from({ length: T + 1 }, () => 0);
    gears_lotate[now_gear] = lotat_direct;

    let left_gear = -1;
    let right_gear = -1;

    if (now_gear === 1) {
        right_gear = now_gear + 1;
    }
    else if (now_gear === T) {
        left_gear = now_gear - 1;
    }
    else {
        left_gear = now_gear - 1;
        right_gear = now_gear + 1;
    }

    // 회전 기어의 왼쪽 기어들 차례로 확인
    let now_lotat_direct = lotat_direct * -1; // 현재 회전 방향
    if (left_gear !== -1) {
        for (let left = left_gear; left > 0; left--) {
            // 왼쪽 기어 A와 A의 오른쪽 기어인 B 기어의 맞닿는 극이 다르면 왼쪽 기어 회전
            if (gear[left][2] != gear[left + 1][6]) {
                gears_lotate[left] = now_lotat_direct;
                now_lotat_direct *= -1;
            }
            // 회전 영향을 안받는다면 종료
            else {
                break;
            }
        }
    }
    // 회전 기어의 오른쪽 기어들 차례로 확인
    now_lotat_direct = lotat_direct * -1;
    if (right_gear !== -1) {
        for (let right = right_gear; right <= T; right++) {
            // 왼쪽 기어 A와 A의 오른쪽 기어인 B 기어의 맞닿는 극이 다르면 오른쪽 기어는 회전
            if (gear[right - 1][2] != gear[right][6]) {
                gears_lotate[right] = now_lotat_direct;
                now_lotat_direct *= -1;
            }
            // 회전 영향을 안받는다면 종료
            else {
                break;
            }
        }
    }

    // 모든 기어 일제히 회전시킴.
    for (let g = 1; g <= T; g++) {
        if (gears_lotate[g] === 0) continue;
        // 시계방향으로 돌아야 함.
        else if (gears_lotate[g] === 1) {
            temp = gear[g].pop();
            gear[g].unshift(temp);
        }
        // 반시계방향으로 돌아야 함.
        else {
            temp = gear[g].shift();
            gear[g].push(temp);
        }
    }
}

let cnt = 0;

for (let i = 1; i <= T; i++) {
    if (gear[i][0] === 1) {
        cnt += 1;
    }
}
console.log(cnt)

 

 


풀이 후기

 

문제가 길고 복잡한 구현 문제도 핵심을 잘 캐치해 로직을 이해하면 어렵지 않게 풀 수 있다고 생각한다.