[백준/C++] 1699번_제곱수의 합

이수진·2022년 2월 2일
0

문제는 다음과 같습니다.

일단, 여러번 시도했지만 실패했던 문제입니다.
틀린것도 나중에 힌트를 얻고나서 왜 틀렸는지 알게되었습니다. ㅋㅋㅋㅋ
분명 쉬워보였는데.. 틀렸습니다🥺

제가 푼 풀이는 다음과 같습니다.

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, i=1, min_cnt=100000;
    cin>>n;

    while(i*i<=n){
      i++;
      if((i+1)*(i+1)>n) break;
    }
    // i는 쓸 수 있는 최대 제곱수: 1~i까지

    for(int j=i; j>=1; j--){
      int N=n;
      int tmp=0;
      int k=j;
      while(N>0 && k>=1){
        while(N>=k*k && k>=1){
          N-=k*k;
          tmp++;
        }
        k--;
      }
      min_cnt=min(min_cnt, tmp);
    }

    
    cout<<min_cnt<<endl;
    return 0;
}

제가 풀었던 풀이 방식은 다음과 같습니다.
먼저, 뺄 수 있는 최대의 제곱수를 구합니다.
그리고 그 수부터 시작해서 뺄 수 있는 경우의 수만큼 빼고 그 때의 최소 항의 개수를 구합니다,
그리고 최대의 제곱수에서 -1씩 빼서 그 다음 제곱수부터 시작해 경우의 수를 구합니다.

예시로 11을 든다면
11의 최대 제곱수는 3입니다.
그래서 첫 번째 경우에는 => (3x3)+(1x1)+(1x1)=11 이어서 항의 수는 3
두 번째 경우에는 => (2x2)+(2x2)+(1x1)+(1x1)+(1x1)=11 이어서 항의 수는 5
마지막 경우에는 => (1x1)...(1x1)=11 이어서 항의 수는 11입니다.

그래서 최소인 3을 구하게 되는데요,
"즉 시작하는 가장 큰 제곱수를 뺄 수 있을 만큼 빼게 되는 게 이 문제를 틀린 이유였습니다."

예시로 숫자 43을 예로 들면

43 = (43 - 2²) + 2² = …
= (43 - 3²) + 3² = …
= (43 - 4²) + 4² = …
= (43 - 5²) + 5² = 18 + 5² -> 18은 3² + 3² = 2개의 최소 항으로 표시 -> 총 항의 갯수 3개
43 = 18 + 5² = 3² + 3²+5² (3개)
= (43 - 6²) + 6² = 7 + 6² -> 7은 1² + 1² + 1² + 2² = 4개의 최소 항으로 표시 -> 총 항의 갯수 5개
43 = 7 + 6² = 1² + 1² + 1² + 2²+6² (5개)
위의 예시처럼, 더 큰 제곱수를 뺀다 해도, 반드시 최소 항의 갯수를 구한다고 보장할 수 없습니다.
43에서 36을 뺐을 때가 아닌, 25를 뺐을 때 최소 항의 갯수 3개를 가집니다.
따라서 단순하게 자연수 N보다 작은 제곱수들 중 가장 큰 것으로만 빼서 계산한다면 틀리게 됩니다.


해답에서는 dp배열을 이용하여 구했는데요,
점화식의 원리는 다음과 같습니다.
N을 최소 제곱수의 합으로 나타내는 경우의 수를 dp[N]에 저장한다고 하면,

dp[N] = i^2 + dp[N-(i^2)]

로 나타낼 수 있습니다.
즉 i는 0부터 ~ i*i<=N 까지 가능하며,
N은 i의 제곱 + dp[N-(i^2)] 의 수로 표현할 수 있기 때문입니다.

즉, 1부터 n까지의 수를 최소 제곱수를 이용해 나타낼 수 있는 경우의 수를 bottom-up방법으로 구해야합니다.

먼저, 0부터 입력받은 n까지의 수를 dp[i]=i; 로 (초기화를 제곱수 1로만 나타낸 경우) 초기화한 후에
그리고 j를 1부터 j*j<=n까지 돌려, 그 중에서도 최소인 min을 찾으면 됩니다.

위에서 설명한 핵심 코드는 다음과 같습니다.

    for(int i=1; i<=n; i++){
        dp[i]=i;
        for(int j=1; j*j<=i; j++){
            dp[i]=min(dp[i], dp[i-j*j]+1);
        }
    }

그리고 전체 코드는 다음과 같습니다.

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    int n; int dp[100001]={0, };
    cin>>n;

    for(int i=1; i<=n; i++){
        dp[i]=i;
        for(int j=1; j*j<=i; j++){
            dp[i]=min(dp[i], dp[i-j*j]+1);
        }
    }
    
    cout<<dp[n]<<endl;
    return 0;
}

2월 8일 화요일 복습)

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int dp[100001]={0, };
    int n; cin>>n;

    for(int i=1; i<=n; i++){
      dp[i]=dp[i-1]+1;
      for(int j=2; j*j<=i; j++){
        dp[i]=min(dp[i], dp[i-j*j]+1);
      }
    }
    
    cout<<dp[n]<<"\n";
    return 0;
}
profile
꾸준히, 열심히, 그리고 잘하자

0개의 댓글