Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++로 배열 요소들의 최소공배수(LCM) 소인수 구하기

이 문제에서는 각 요소가 1 ≤ arr[i] ≤ 1012 범위에 있는 배열이 주어집니다. 우리의 목표는 배열의 모든 요소에 대한 최소공배수(LCM)의 모든 소인수를 출력하는 것입니다.

문제 이해하기

간단한 예제를 통해 문제를 살펴보겠습니다.

입력: array = {2 , 5 , 15}
출력: 2 3 5
설명: LCM = 30
30의 인수 = 2 × 3 × 5

배열 {2, 5, 15}의 최소공배수는 30이며, 30을 소인수분해하면 2 × 3 × 5가 됩니다. 따라서 정답은 2, 3, 5입니다.

접근 방법

가장 직관적인 방법은 먼저 배열 요소들의 LCM을 구한 뒤, 그 LCM의 약수를 구하고 그중에서 소수만 걸러내는 것입니다.

하지만 배열 요소가 최대 1012까지 가능하기 때문에, LCM 자체가 매우 커질 수 있습니다. 이렇게 큰 수의 LCM을 직접 계산하면 오버플로우와 성능 저하 문제가 발생하여 비효율적입니다. 따라서 다른 방식으로 접근해야 합니다.

핵심 아이디어

여기서 중요한 수학적 성질 하나를 활용합니다.

“어떤 수들의 소인수는 그 수들의 LCM의 소인수이기도 하다”

즉, LCM을 직접 구할 필요 없이 배열의 각 요소를 개별적으로 소인수분해한 후 등장하는 소수들을 모두 모으면, 그것이 곧 LCM의 소인수 집합이 됩니다. 이때 소수 목록은 Sundaram의 체(Sieve of Sundaram) 알고리즘을 이용해 미리 생성합니다.

전체 과정을 정리하면 다음과 같습니다.

  1. Sundaram의 체를 이용해 필요한 범위(√1012 = 106) 이하의 모든 소수를 미리 구합니다.
  2. 배열의 각 요소를 순회하며 소인수분해를 수행합니다.
  3. 발견된 각 소인수를 factors 배열에 표시합니다.
  4. 마지막으로 factors 배열에서 표시된 소수들을 모두 출력합니다.

C++ 구현 코드

#include <bits/stdc++.h>
using namespace std;
const int MAX = 1000000;
typedef long long int ll;
vector <int> primeNumbers;
void findPrimeNumbers() {
   int n = MAX;
   int nNew = (n)/2;
   bool marked[nNew + 100];
   memset(marked, false, sizeof(marked));
   int tmp=sqrt(n);
   for (int i=1; i<=(tmp-1)/2; i++)
      for (int j=(i*(i+1))<<1; j<=nNew; j=j+2*i+1)
         marked[j] = true;
   primeNumbers.push_back(2);
   for (int i=1; i<=nNew; i++)
   if (marked[i] == false)
   primeNumbers.push_back(2*i + 1);
}
void printPrimeLCM(ll arr[], int n ) {
   findPrimeNumbers();
   int factors[MAX] = {0};
   for (int i=0; i<n; i++) {
      ll copy = arr[i];
      int sqr = sqrt(copy);
      for (int j=0; primeNumbers[j]<=sqr; j++){
         if (copy%primeNumbers[j] == 0){
            while (copy%primeNumbers[j] == 0)
            copy = copy/primeNumbers[j];
            factors[primeNumbers[j]] = 1;
         }
      }
      if (copy > 1)
      factors[copy] = 1;
   }
   if (factors[2] == 1)
      cout<<2<<"\t";
   for (int i=3; i<=MAX; i=i+2)
      if (factors[i] == 1)
         cout<<i<<"\t";
}
int main() {
   ll arr[] = {20, 10, 15, 60};
   int n = sizeof(arr)/sizeof(arr[0]);
   cout<<"Prime factors in the LCM of the numbers of the array are :\n";
   printPrimeLCM(arr, n);
   return 0;
}

출력 결과

Prime factors in the LCM of the numbers of the array are :
2  3   5

코드 설명

1. findPrimeNumbers()

Sundaram의 체 알고리즘을 사용하여 106 이하의 모든 소수를 생성합니다. 이 알고리즘은 홀수만 대상으로 합성수를 표시하기 때문에 일반적인 에라토스테네스의 체보다 메모리를 절반 수준으로 줄일 수 있습니다.

2. printPrimeLCM()

배열의 각 요소에 대해 √arr[i] 이하의 소수들로 나누어 소인수분해를 진행합니다. 특정 소수로 나누어떨어지면 해당 소수를 factors 배열에 표시하고, 더 이상 나누어떨어지지 않을 때까지 반복해서 나눕니다.

모든 소수 검사가 끝난 후 남은 값이 1보다 크다면, 그 값 자체가 소인수이므로 이 역시 factors 배열에 표시합니다.

3. 결과 출력

factors 배열을 순회하면서 표시된(값이 1인) 소수들을 차례대로 출력합니다. 짝수인 소수는 2뿐이므로 2를 먼저 처리하고, 이후에는 홀수만 검사하여 효율성을 높였습니다.

복잡도 분석

  • 시간 복잡도: 소수 생성에 O(N log log N), 각 배열 요소의 소인수분해에 약 O(√M / log √M)이 소요됩니다. 여기서 N은 체의 크기, M은 배열 요소의 최댓값입니다.
  • 공간 복잡도: 소수 목록과 factors 배열 저장을 위해 O(MAX)의 공간이 필요합니다.

이처럼 LCM을 직접 계산하지 않고 각 요소의 소인수를 결합하는 방식을 사용하면, 매우 큰 수가 포함된 배열에서도 오버플로우 없이 안정적으로 LCM의 소인수를 구할 수 있습니다.