이 포스팅은 Algorithm Problem Solving 시리즈 113 편 중 89 번째 글 입니다.

  • Part 1 - 백준(1003번): 피보나치 함수
  • Part 2 - 백준(1010번): 다리놓기
  • Part 3 - 백준(1012번): 유기농 배추
  • Part 4 - 백준(10159번): 저울
  • Part 5 - 백준(10164번): 격자상의 경로
  • Part 6 - 백준(1018번): 체스판 다시 칠하기
  • Part 7 - 백준(1018번): 체스판 다시 칠하기
  • Part 8 - 백준(1022번): 소용돌이 예쁘게 출력하기
  • Part 9 - 백준(10775번): 공항
  • Part 10 - 백준(10816번): 숫자 카드2
  • Part 11 - 백준(10816번): 숫자카드 2
  • Part 12 - 백준(10819번): 차이를 최대로
  • Part 13 - 백준(10830번): 행렬 곱셈
  • Part 14 - 백준(10844번): 쉬운 계단수
  • Part 15 - 백준(10868번): 최소값
  • Part 16 - 백준(1092번): 배
  • Part 17 - 백준(11003번): 최솟값 찾기
  • Part 18 - 백준(1102번): 발전소
  • Part 19 - 백준(11048번): 이동하기
  • Part 20 - 백준(11053번): 가장 긴 증가하는 부분 수열
  • Part 21 - 백준(11054번): 가장 긴 바이토닉 부분 수열
  • Part 22 - 백준(11055번): 가장 긴 증가하는 부분 수열2
  • Part 23 - 백준(11057번): 오르막 수
  • Part 24 - 백준(1120번): 문자열
  • Part 25 - 백준(11403번): 경로 찾기
  • Part 26 - 백준(11404번): 플루이드
  • Part 27 - 백준(1149번): RGB 거리
  • Part 28 - 백준(11559번): puyo puyo
  • Part 29 - 백준(11655번): ROT13
  • Part 30 - 백준(1167번): 트리의 지름
  • Part 31 - 백준(11722번): 가장 감소하는 부분 수열
  • Part 32 - 백준(12015번): 가장 긴 증가하는 부분 수열(LIS) 2
  • Part 33 - 백준(12851번): 숨바꼭질2
  • Part 34 - 백준(12852번): 1로 만들기 2
  • Part 35 - 백준(12865번): 평범한 배낭
  • Part 36 - 백준(1300번): K번째 수
  • Part 37 - 백준(1325번): 효율적인 해킹
  • Part 38 - 백준(13549번): 숨바꼭질3
  • Part 39 - 백준(13913번): 숨바꼭질4
  • Part 40 - 백준(14002번): 가장 긴 증가하는 부분 수열 4
  • Part 41 - 백준(1431번): 시리얼 넘버
  • Part 42 - 백준(1436번): 영화감독 숌
  • Part 43 - 백준(14499번): 주사위 굴리기
  • Part 44 - 백준(14888번): 연산자 끼워넣기
  • Part 45 - 백준(14889번): 스타트와 링크
  • Part 46 - 백준(14891번): 톱니바퀴
  • Part 47 - 백준(15658번): 연산자 끼워넣기 2
  • Part 48 - 백준(15686번): 치킨 배달
  • Part 49 - 백준(1697번): 숨바꼭질
  • Part 50 - 백준(1697번): 숨바꼭질
  • Part 51 - 백준(1707번): 이분 그래프(Bipartite Graph)
  • Part 52 - 백준(1708번): 볼록 껍질
  • Part 53 - 백준(17136번): 색종이 붙이기
  • Part 54 - 백준(1717번): 집합의 표현
  • Part 55 - 백준(17298번): 오큰수
  • Part 56 - 백준(17626번): Four Squares
  • Part 57 - 백준(18870번): 좌표 압축
  • Part 58 - 백준(1890번): 점프
  • Part 59 - 백준(1918번): 후위 표기식
  • Part 60 - 백준(1929번): 소수 구하기
  • Part 61 - 백준(1963번): 소수 경로
  • Part 62 - 백준(1965번): 상자넣기
  • Part 63 - 백준(1976번): 여행가자
  • Part 64 - 백준(1987번): 알파벳
  • Part 65 - 백준(1992번): 쿼드트리
  • Part 66 - 백준(2003번): 수들의 합2
  • Part 67 - 백준(20040번): 사이클 게임
  • Part 68 - 백준(2042번): 구간 합 구하기
  • Part 69 - 백준(2108번): 통계학
  • Part 70 - 백준(2110번): 공유기 설치
  • Part 71 - 백준(2156번): 포도주 시식
  • Part 72 - 백준(2193번): 이친수
  • Part 73 - 백준(2231번): 분해합
  • Part 74 - 백준(2250번): 트리의 높이와 너비
  • Part 75 - 백준(2293번): 동전 1
  • Part 76 - 백준(2294번): 동전 2
  • Part 77 - 백준(2343번): 기타 레슨
  • Part 78 - 백준(2468번): 안전 영역
  • Part 79 - 백준(2512번): 예산
  • Part 80 - 백준(2529번): 부등호
  • Part 81 - 백준(2565번): 전깃줄
  • Part 82 - 백준(2581번): 소수
  • Part 83 - 백준(2583번): 영역구하기
  • Part 84 - 백준(2630번): 색종이 만들기
  • Part 85 - 백준(2644번): 촌수계산
  • Part 86 - 백준(2667번): 단지번호붙이기
  • Part 87 - 백준(2751번): 수 정렬하기 2
  • Part 88 - 백준(2798번): 블랙잭
  • Part 89 - This Post
  • Part 90 - 백준(2986번): 파스칼
  • Part 91 - 백준(3015번): 오아시스 재결합
  • Part 92 - 백준(3190번): 뱀
  • Part 93 - 백준(3190번): 벽 부수고 이동하기
  • Part 94 - 백준(4195번): 친구 네트워크
  • Part 95 - 백준(4948번): 베르트랑 공준
  • Part 96 - 백준(4963번): 섬의 개수
  • Part 97 - 백준(4991번): 로봇 청소기
  • Part 98 - 백준(5373번): 큐빙
  • Part 99 - 백준(5437번): 불
  • Part 100 - 백준(6171번): 땅따먹기
  • Part 101 - 백준(6603번): 로또
  • Part 102 - 백준(6603번): 로또
  • Part 103 - 백준(7562번): 나이트의 이동
  • Part 104 - 백준(7568번): 덩치
  • Part 105 - 백준(7576번): 토마토
  • Part 106 - 백준(7579번): 앱
  • Part 107 - 백준(7785번): 회사에 있는 사람
  • Part 108 - 백준(9012번): 괄호
  • Part 109 - 백준(9020번): 골드바흐의 추측
  • Part 110 - 백준(9184번): 신나는 함수 실행
  • Part 111 - 백준(9251번): LCS
  • Part 112 - 백준(9421번): 소수상근수
  • Part 113 - 프로그래머스: 가장 큰 정사각형 찾기
▼ 목록 보기

골드4 : 수학 문제이다.

생각

문제 이름 부터 뭔가 마음에 들지 않았다. 풀이 방법은 바로 떠올랐는데 왜 요즘 이런게 구현이 안되는지… score가 최대공약수를 구하는 것이기 때문에 소인수 분해를 떠올릴 수 있고, 그러기 위해서는 소수를 구해야 한다. 그러니 소수를 구하는 가장 빠른 알고리즘인 에라토스 테네스의 체를 떠올릴 수 있다.

규칙의 이해

8 24 9
$2^3$ $2^3\cdot 3$ $3^2$

문제의 입력은 다음과 같다. 이 때 A,B를 정해서 문제에서 원하는 규칙에 따라 진행하는 것은, 각각의 수를 이루는 소수들 중 하나를 떼서 준다로 압축할 수 있다.

A : 8->4 24 B : 9->18
$2^2$ $2^3\cdot3$ $2\cdot 3^2$

이런 규칙을 가지고 문제에서 원하는 것을 구하기 위해서는 어떠한 상황이여야 하는지 생각해 보자.

최대 숫자

내가 가진 소인수의 개수를 서로에게 나눠주는 과정 속에서 최대한 잘 나눠가졌을 때, 공통된 숫자가 무엇이냐?

상당히 공리주의적 관점이다. 위의 조건을 만족하려면 각각이 가진 개수를 모두 파악한 후, 인원수로 나눈것이 모두가 고루고루 나누는 최종 숫자이다.

8 24 9 1728 12
$2^3$ $2^3\cdot 3$ $3^2$ $2^6\cdot3^3$ $2^2\cdot 3$

이 문제에서는 12라는 숫자가 답이 될 수 있겠다.

최소 이동 횟수

이제 답이 되는 후보를 알았다. 각각의 숫자를 그 숫자를 위해 움직여야 하는 횟수가 있을 것이다.

8 24 9
$2^3$ $2^3\cdot 3$ $3^2$
2 1 3
  • 8의 경우 12가 되기 위해서 2를 버리고 3을 얻어야 한다.
  • 24의 경우 2를 버려야 한다.
  • 9의 경우 2를 2개 얻고, 3을 버려야 한다.

총 6번의 움직임이 필요하지만, 이것은 하나의 행동이 2번씩 중복되어 나타났다. 이 것을 해소하기 위해서는 최종적으로 답을 내기 위해 2로 나누거나, 하나의 행동만을 제약해서 counting을 하면 된다. (답이 요구하는 개수보다 작을 경우만 센다)

구현

구현하기 위해 필요한 것을 생각해본다.

에라토스테네스의 체

에라토스테네스의 체는, 소수를 구하는 방법 중 가장 빠른 방법이다.
에라토스테네스의 체

  1. n까지의 소수는 $\sqrt{n}$ 범위 안에있는 소수를 가지고 구할 수 있다.
    • 소인수 분해를 하면, 해당 수의 제곱근 보다 작은 소인수를 가지고 모든 약수를 구할 수 있다.
  2. 소수의 배수는 소수가 아니다.

에라토스테네스의 체

120까지의 소수를 구하기 위해서는 $\sqrt{120} = 10.9…$즉, 11보다 작은 수, 10까지의 수를 가지고 120까지의 소수를 구할 수 있다.

알고리즘

void SieveOfEratosthenes(){
    for (int i = 2; i <= MAX; i++)
        isPrime[i] = true;
    for (int i = 2; i*i <= MAX; i++) {
        if (!isPrime[i]) continue;
        for (int j = i*i; j <= MAX; j+=i)
            isPrime[j] = false;
    }
}

이 때, j를 $i^2$ 부터 탐색하는 것에 주목하자. i 이전의 배수들은 그 전의 소수가 이미 걸렀기 때문이다. 해당 알고리즘의 시간 복잡도는 $n\cdot \sqrt{n}$ 이다.

나머지 필요한 것들

  1. 에라토스테네스의 체를 통해 얻은 소수를 저장할 변수가 필요하다.
    • vector<int> primelist
  2. 각각의 입력되는 숫자에 대해 소인수 분해를 하여 담아둘 변수가 필요하다.
    • vector<vector<int>> input (n, vector<int> (primeNumberSize, 0))
    • n개의 숫자에 대해 1000000까지 발생하는 소수의 개수 만큼의 배열이 필요하다.
    • j는 발생하는 소수를 오름차순으로 정렬했을 때의 index이다.
  3. 전체 소인수들의 개수를 저장할 변수가 필요하다.
    • vector<int> whole (primelist.size(), 0)

이제 해야 할 일은 순서도를 작성하는 것이다.

알고리즘

  1. 에라토스테네스의 체로 1000000까지의 소수를 구한다.
  2. 이 소수를 primelist에 저장한다.
  3. primelist의 개수만큼 whole, input 의 크기를 공간을 만든다.
  4. 각각의 input이 어떻게 소인수 분해되는지 구한다.
  5. 구하는 도중에 전체 배열에 추가한다.
  6. 다 구했다면 최종적으로 위에서 구한 방법으로 답을 구한다.

Code

#include <iostream>
#include <cmath>
#include <algorithm>
#include <functional>
#include <vector>
#include <set>

using namespace std;
typedef long long ll;

#define MAX 1000000
bool isPrime[MAX + 1];
int whole[MAX + 1];
//vector<int> primelist;
int N;


void SieveOfEratosthenes(){
    for (int i = 2; i <= MAX; i++)  isPrime[i] = true;
    for (int i = 2; i*i <= MAX; i++) {
        if (!isPrime[i]) continue;
//        primelist.push_back(i); // 여기다 추가하면 1000 범위내의 소수만 들어간다..
        for (int j = i*i; j <= MAX; j+=i) isPrime[j] = false;
    }
}


int main(){
    SieveOfEratosthenes();
    cin >> N;
    vector<int> primelist;
    for (int i = 1; i <= MAX; i++) if (isPrime[i]) primelist.push_back(i);

    vector<vector<int>> input(N, vector<int>(primelist.size(), 0));
    for (int i = 0; i < N; i++) {
        int num;
        cin >> num;
        for (int j = 0; j < primelist.size(); j++) {
            if (num == 1) break;
            while (num % primelist[j] == 0) {
                num /= primelist[j];
                whole[primelist[j]]++;
                input[i][j]++;
            }
        }
    }

    int ans = 1, count = 0;

    for (int i = 0; i < primelist.size(); i++) {
        int currentWholePrimeCount = whole[primelist[i]]/N;
        for (int j = 0; j < N; j++) {
            if (currentWholePrimeCount > input[j][i])
                count += (currentWholePrimeCount-input[j][i]);
        }
        ans *= pow(primelist[i], currentWholePrimeCount);
    }

    cout << ans << " " << count << '\n';
    return 0;
}


//
//  main.cpp
//  test
//
//  Created by 최완식 on 2021/03/15.
//

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <map>
using namespace std;
typedef long long ll;

bool isPrime[1001];
vector<int> Prime;
map<int, int> temp;
map<int, int> total;
vector<map<int, int>> eachNum;

void div(int a){
    for (auto now: Prime) {
        if (a%now == 0) {
            temp[now]++;
            total[now]++;
            a /= now;
            div(a);
            return;
        }
    }
        
    if (a != 1) {
        total[a]++;
        temp[a]++;
    }
}

int main(){
    for (int i = 2; i <= 1000; i++) {
        if (isPrime[i]) {
            continue;
        }
        Prime.push_back(i);
        for (int j = i*i; j <= 1000; j+=i) {
            isPrime[j] = true;
        }
    }
    
    int n;
    cin >> n;
    
    for (int i = 0; i < n; i++) {
        int num;
        cin >> num;
        temp.clear();
        div(num);
        eachNum.push_back(temp);
        
        
    }
    
    ll ans = 1LL;
    
    for (auto a: total){
        total[a.first] = a.second/n;
        ans *= pow(a.first, a.second/n);
    }
    
    int count = 0;
    
    for (int i = 0; i < n; i++) {
        for (auto a: total){
            if (total[a.first] > eachNum[i][a.first]) {
                count += total[a.first] - eachNum[i][a.first];
            }
        }
    }
    
    cout << ans << ' ' << count << endl;
    
    
    
    
    return 0;
}

Reference

백준(2904번) - 수학은 너무 쉬워