본문 바로가기
Algorithm/BOJ

[BOJ]1699번: 제곱수의 합(java, c++)

by HBGB 2019. 10. 19.

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

 

1699번: 제곱수의 합

어떤 자연수 N은 그보다 작거나 같은 제곱수들의 합으로 나타낼 수 있다. 예를 들어 11=32+12+12(3개 항)이다. 이런 표현방법은 여러 가지가 될 수 있는데, 11의 경우 11=22+22+12+12+12(5개 항)도 가능하다. 이 경우, 수학자 숌크라테스는 “11은 3개 항의 제곱수 합으로 표현할 수 있다.”라고 말한다. 또한 11은 그보다 적은 항의 제곱수 합으로 표현할 수 없으므로, 11을 그 합으로써 표현할 수 있는 제곱수 항의 최소 개수는

www.acmicpc.net

 

방법 1: Top-down 

 

c++소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <cmath>
 
using namespace std;
const int MAX = 100000;
int cnt[MAX + 1];
 
/*
    D[i] : i를 제곱수의 합으로 나타냈을 때, 최소 항의 개수
 
    점화식 : D[i] = min(D[i - j^2]) + 1
*/
int get_min_pow_sum(int n)
{
    if (n == 0)
    {
        return 0;
    }
    if (cnt[n] > 0)
    {
        return cnt[n];
    }
 
    int min = n;
    for (int i = 1; i * i <= n; i++)
    {
        if (n - i * i >= 0)
        {
            int tmp = get_min_pow_sum(n - i * i) + 1;
            min = (min > tmp) ? tmp : min;
        }
    }
    return cnt[n] = min;
}
 
int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
 
    int n;
    cin >> n;
 
    get_min_pow_sum(n);
 
    cout << cnt[n];
 
    return 0;
}
Colored by Color Scripter
 
 

 

방법 2: Bottom-up

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.*;
 
public class Main {
    public static void main(String[] args) {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
            int num = Integer.parseInt(br.readLine());
            int[] arrMin = new int[num + 1];
            
            for (int i = 1; i <= num; i++) {
                arrMin[i] = i;
                
                for (int j = 1; j * j<= i; j++) {
                    
                    if (arrMin[i] > arrMin[i - j * j] + 1) {
                        arrMin[i] = arrMin[i - j * j] + 1;
                    }
                }
            }
 
            System.out.println(arrMin[num]);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Colored by Color Scripter

 

c++소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
 
using namespace std;
 
int main()
{
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
 
    int n;
    cin >> n;
 
    const int MAX = 100000;
    int cnt[MAX + 1];
 
    /*
        D[i] : i를 제곱수의 합으로 나타냈을 때, 최소 항의 개수
        점화식 : D[i] = min(D[i - j^2]) + 1
    */
    cnt[0= 0;
    for (int i = 1; i <= n; i++)
    {
        cnt[i] = i;
        for (int j = 1; j * j <= i; j++)
        {
            if (cnt[i] > cnt[i - j * j] + 1)
            {
                cnt[i] = cnt[i - j * j] + 1;
            }
        }
    }
    
    cout << cnt[n];
 
    return 0;
}
Colored by Color Scripter

 

 

바텀업이 5배이상 빨랐다. 

주어진 수 N 이하의 모든 수를 한번씩 방문 해야 하는 문제라면, N이 크면 클수록

do(1) ~ do(N) 을 모두 돌아야 하는 탑다운 방식의 시간부담이 그만큼 늘어나기 때문으로 보인다.

 

 

TIP1

주어진 자연수 N을 이렇게 제곱수들의 합으로 표현할 때에 그 항의 최소개수를 구하는 프로그램을 작성하시오.

 

32를 예로 들어 생각해보자.

 

 

 

32는 위와같이 2가지 이상의 방법으로 표현할 수 있다.

 

만약 무조건 가장 큰 제곱수부터 시작해서 남은 수를 채워가는 식으로 한다면, 

32 같은 테스트 케이스의 경우에 틀리게 된다. 

1
2
3
4
5
6
7
8
9
10
11
for (long i = num / 2; i >= 0; i--) { //( X )
    
    while (i * i <= num) {
        num = num - i * i;
        count++;
    }
    
    if (num == 0) {
        break;
    }
}
Colored by Color Scripter

처음에 짰다가 틀린 코드...

 

 

그렇기 때문에 이 문제는 다시 한번 생각해보아야 한다.

그렇다.

이 문제는 사실 N이 주어진 게 아니라, 1 ~ N 까지의 수가 주어진 문제인 것이다. 

즉, A[N]을 제곱수의 합으로 표현할 때의 최소개수를 D[N] 이라 하는 수열을 구해야 한다. 

 

 

따라서 1 ≤ K ≤ N 일때,

① D[1] 부터 구해나가야 한다.

② D[K] 일 때, D[1] ~ D[K - 1] 을 반복해서 확인하며

조건문을 충족하는 D[K - 제곱수] + 1 을 찾아야 한다.

1
2
3
4
5
6
for (int j = 1; j * j<= i; j++) {
    
    if (arrMin[i] > arrMin[i - j * j] + 1) {
        arrMin[i] = arrMin[i - j * j] + 1;
    }
}
Colored by Color Scripter

 

 

D[32] 를 다시 예로 들어보면,

j = 4일 때, D[32] = D[32 - 4 * 4] + 1 = D[16] + 1 = 2  (D[16] =1)

j = 5일 때, D[32] = D[32 - 5 * 5] + 1 = D[7] + 1 =  5  (D[7] = 4)

j = 5일 때 비교되는 수 5는

j = 4일 때 저장된 D[32] 값 2보다 크기 때문에 조건문을 충족하지 못하고 PASS

 

 

 

댓글