본문 바로가기

카테고리 없음

백준 6064: 카잉 달력

 

📃 문제: 백준 6064_카잉 달력

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

 

 

M과 N보다 작거나 같은 두 개의 자연수 x, y를 가지고 각 년도를 <x:y>와 같은 형식으로 표현한다. 이 세상의 시초에 해당하는 첫 번째 해를 <1:1>로 표현하고, 두 번째 해를 <2:2>로 표현한다. <x:y>의 다음 해를 표현한 것을 <x':y'>이라고 하자. 만일 x < M 이면 x' = x + 1이고, 그렇지 않으면 x' = 1이다.

같은 방식으로 만일 y < N이면 y' = y + 1이고, 그렇지 않으면 y' = 1이다. <M:N>은 그들 달력의 마지막 해로 판단한다.

예를 들어, M = 10 이고 N = 12라고 하자. 첫 번째 해는 <1:1>로 표현되고, 11번째 해는 <1:11>로 표현된다. <3:1>은 13번째 해를 나타내고, <10:12>는 마지막인 60번째 해를 나타낸다.

네 개의 정수 M, N, x와 y가 주어질 때, <M:N>이 카잉 달력의 마지막 해라고 하면 <x:y>는 몇 번째 해를 나타내는지 구하는 프로그램을 작성하라.

 

예제 입력)

3
10 12 3 9
10 12 7 2
13 11 5 6

 

예제 출력)

33
-1
83

 

 

🥈 난이도: 실버 1

 

 

😲 사용한 개념: 유클리드 호제법, 수학, 부르트포스

 

    ☝️ 유클리드 호제법이란?

유클리드 호제법은 두 수의 최대공약수를 구하는 알고리즘이다.

(호제법 = 두 수가 서로 상대방의 수를 나누어서 결국 원하는 수를 얻는 알고리즘)

 

유클리드 호제법으로 최대공약수를 구하는 방법에 대해 알아보자.

먼저, 큰 수를 작은 수로 나눈 나머지를 구한다.(=MOD 연산 수행)

그 다음에, 나눴던 수와 나머지로 또 MOD 연산을 한다.

이 과정을 MOD 연산의 결과가 0이 될 때까지 반복한다.

나머지가 0이 되었을 때, 마지막 계산에서 나누는 수로 사용된 수가 입력받은 처음 두 수의 최대공약수가 된다!

간단하게 말하면, 유클리드 호제법은 MOD 연산을 반복하면 된다.

아래 그림을 참고해서 몇번 계산을 해보면 감이 올 것이다.

 

 

 

이를 소스코드로는 아래와 같이 구현할 수 있다.

// 최대공약수 구하기
int gcd(int a, int b) {
    int c = a % b;
    while (c != 0) {
        a = b;
        b = c;
        c = a % b;
    }
    return b;
}

 

 

최소공배수의 경우는 직관적으로 (두 수의 곱) / (두 수의 최대공약수)를 구하면 그게 바로 최소 공배수가 될 것이다.

//최소공배수 구하기
int lcm(int a, int b) {
    return (a * b) / gcd(a, b);
}

 

 

📖 풀이 사고 과정

 '카이 달력' 문제의 조건을 잘 이해하고, 유클리드 호제법의 개념을 알고 있다면 어렵지 않게 해결할 수 있는 문제이다.

달력의 각 X, Y는 M과 N을 주기로 순회한다.

예를 들어 M = 12, N = 11이라면

X: 1 -> 2-> 3-> ... ->12 -> 1 -> 2 -> ...

Y: 1 -> 2-> 3-> ... ->11 -> 1 -> 2 -> ...

이런식으로 날짜가 순회할것이다.

이때 M과 N의 최소공배수가 되는 지점에서 날짜가 다시 <1, 1> (=시작지점)으로 돌아올 것이고, 같은 패턴을 반복하며 날짜 지나갈 것이다.

 

따라서 특정 <x, y>일이되는 일수를 찾기 위해서는 M과 Y의 최소공배수만큼까지 탐색을 해보면 가능한 날짜인지 아닌지와, 해당 날짜에 도달하기 위해서 몇일이 지나야 하는지를 구할 수 있다.

 

즉,

1) M, N의 최소공배수를 구하고,

2)최소공배수에 해당하는 일수만큼 순회하면서 (i % M) ==  X && (i % N) == Y 조건을 만족하는 일 수를 구하면 

문제를 해결할 수 있다.

 

 

👩‍💻 구현 코드(c++ 사용)

#include <iostream>
#include <vector>
using namespace std;

// 최대공약수 구하기
int gcd(int a, int b) {
    int c = a % b;
    while (c != 0) {
        a = b;
        b = c;
        c = a % b;
    }
    return b;
}
//최소공배수 구하기
int lcm(int a, int b) {
    return (a * b) / gcd(a, b);
}


int calculateDate(int M, int N, int X, int Y) {
    int lcmResult, result = -1;
    // 1) M, N의 최소공배수 구하기
    lcmResult = lcm(M, N);
    // 2) x, y가 몇번째 해인지 구하기
    result = -1;
    for (int i = X; i <= lcmResult; i++) {
        if (i % M == X && i % N == Y) {
            result = i;
            break;
        }
    }
    for (int i = X; i <= lcmResult; i +=M) {
        if (i % N == Y % N) {
            result = i;
            break;
        }
    }
    return result;
}

int main(void) {
    int n, M, N, X, Y;
    vector<int> answerList;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> M >> N >> X >> Y;
        int result = calculateDate(M, N, X, Y);
        answerList.push_back(result);
    }
    for (int answer : answerList) {
        cout << answer << endl;
    }
    return 0;
}
 

원래는 

조건) i = X부터 lcmResult까지 1씩 증가하면서 반복한다.

목적) i % M == X 그리고 i % N == Y를 동시에 만족하는 값을 찾는다.

이 때,

i % M == X 조건이 있기 때문에, 사실상 i는 X와 같은 나머지를 갖는 M의 배수로 제한된다.

 -> 이 조건만으로도 i의 후보는 X, X+M, X+2M, ...로 한정된다.

 

그렇기 때문에 M과 N의 최소공배수만큼의 일수를 일일히 순회하지 않고, 반복문의 효율을 높이기 위해서

 

i = X부터 시작하지만, 이번에는 M씩 증가하도록 소스코드를 작성하였다.

결론적으로,

이미 i는 X와 같은 나머지를 갖는 M의 배수이므로, 첫 번째 코드의 i % M == X 조건이 자동으로 만족된다.

따라서 두 번째 코드에서는 굳이 i % M == X를 체크할 필요 없이, i % N == Y만 확인하면 된다.

이때 Y%N은 Y>N이 될 경우를 방지하기 위함이다.