궤도

[백준] 2981번 : 검문 본문

💻 현생/⛓ 알고리즘

[백준] 2981번 : 검문

영이오 2021. 3. 24. 19:37

문제

 


본문

 

입력 A, B, C가 있다고 하자. 이 수들을 M으로 나눴을 때의 나머지가 전부 같으니 이를 r이라고 하자.

그리고 입력된 수를 M으로 나눴을 때의 몫을 각각 x, y, z라고 하자. 별로 중요하진 않은 애들이다.

 

정리하면

A = M*x + r

B = M*y + r

C = M*z + r

이 된다.

 

여기서 A-B와 B-C를 해보겠다.

A-B = M(w-x)

B-C = M(x-y)

편의상 (w-x)를 k로 (x-y)를 l로 놓으면

A-B = Mk, B-C = Ml이 된다.

 

그니까 M은 (A-B)와 (B-C)의 공약수라는 것이다. 이걸 일반화하면

N개의 숫자가 있는 집합 X로 만들 수 있는 모든 K(K=A-B, A∈B∈X, A≠B)의 공약수가 M이 된다는 것이다.

쉽게 풀어쓰면 집합 X에서 숫자 2개 뽑아서 걔네 둘 빼서 만든 모든 수의 공약수가 M이란 것이다.

 

공약수는 어떻게 구할까? 최대공약수의 약수가 공약수다.


소스코드

 

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

long long num_arr[100];
vector<int> v;

int gcd(int a, int b) { //유클리드 호제법
    int temp, n;

    if (a < b) {
        temp = a;
        a = b;
        b = temp;
    }
    while (b != 0) {
        n = a % b;
        a = b;
        b = n;
    }
    return a;
}

void findM(int N) { //제일 앞에 두개 빼고(초기값) 그 뒤부터 인접한 애들끼리 빼서 최대공약수 구하기
    long long g = num_arr[1] - num_arr[0];

    for (int i = 2; i < N; i++)
        g = gcd(g, num_arr[i] - num_arr[i - 1]);
    for (int i = 2; i * i <= g; i++) { //약수를 구하는 빠른 방법
        if (g % i == 0) {
            v.push_back(i);
            v.push_back(g / i);
        }
    }
    v.push_back(g);
}

int main() { //수학적 지식 필요
    cin.tie(NULL);
    ios_base::sync_with_stdio(false);

    int N;

    cin >> N;
    for (int i = 0; i < N; i++)
        cin >> num_arr[i];
    sort(num_arr, num_arr + N);
    findM(N);
    sort(v.begin(), v.end()); //정렬
    v.erase(unique(v.begin(), v.end()), v.end()); //중복제거(정렬하고 해야한다고 함)
    for (int i = 0; i < v.size(); i++)
        cout << v[i] << ' ';
}

전체 소스코드다.

 

void findM(int N) { //제일 앞에 두개 빼고(초기값) 그 뒤부터 인접한 애들끼리 빼서 최대공약수 구하기
    long long g = num_arr[1] - num_arr[0];

    for (int i = 2; i < N; i++)
        g = gcd(g, num_arr[i] - num_arr[i - 1]);
    for (int i = 2; i * i <= g; i++) { //약수를 구하는 빠른 방법
        if (g % i == 0) {
            v.push_back(i);
            v.push_back(g / i);
        }
    }
    v.push_back(g);
}

findM 함수에서는 가능한 M을 전부 찾는다.

가능한 모든 수의 조합을 뽑아서 계산하는 것보다는 연쇄적으로 최대공약수를 구해나가는게 더 빠를 것이다.

 

그니까

A-B와 B-C의 최대공약수(g)를 구하고 g와 C-D의 최대공약수를 구하고...하는 식으로 구한다는 뜻이다.

main에서 숫자 배열을 오름차순 정렬했기 때문에 음수가 나올 일은 없다.

 

그렇게 구한 최종적인 최대공약수 g의 약수를 구해 배열 v에 넣어준다.

 

int gcd(int a, int b) { //유클리드 호제법
    int temp, n;

    if (a < b) {
        temp = a;
        a = b;
        b = temp;
    }
    while (b != 0) {
        n = a % b;
        a = b;
        b = n;
    }
    return a;
}

최대공약수를 구할 때는 유클리드 호제법을 이용했다.

 

    sort(v.begin(), v.end()); //정렬
    v.erase(unique(v.begin(), v.end()), v.end()); //중복제거(정렬하고 해야한다고 함)
    for (int i = 0; i < v.size(); i++)
        cout << v[i] << ' ';

findM 함수로 구한 M이 들어있는 배열 v에는 중복된 값이 있을 수 있으므로 main에서 중복을 제거한 뒤 출력한다.

Comments