https://www.acmicpc.net/problem/1699
문제
어떤 자연수 N은 그보다 작거나 같은 제곱수들의 합으로 나타낼 수 있다. 예를 들어 11=32+12+12(3개 항)이다. 이런 표현방법은 여러 가지가 될 수 있는데, 11의 경우 11=22+22+12+12+12(5개 항)도 가능하다. 이 경우, 수학자 숌크라테스는 “11은 3개 항의 제곱수 합으로 표현할 수 있다.”라고 말한다. 또한 11은 그보다 적은 항의 제곱수 합으로 표현할 수 없으므로, 11을 그 합으로써 표현할 수 있는 제곱수 항의 최소 개수는 3이다.
주어진 자연수 N을 이렇게 제곱수들의 합으로 표현할 때에 그 항의 최소개수를 구하는 프로그램을 작성하시오.
입력
첫째 줄에 자연수 N이 주어진다. (1 ≤ N ≤ 100,000)
출력
주어진 자연수를 제곱수의 합으로 나타낼 때에 그 제곱수 항의 최소 개수를 출력한다.
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int main()
{
vector<int> dp(100001,9999);//제곱합의 최솟값을 담을 벡터
dp[1] = 1;
int N;
cin >> N;
for (int i = 2; i <= N; i++)
{
for (int j = 1; j * j< i+1; j++)
{
if (i == j * j)
{
dp[i] = 1;
break;
}
dp[i] = min(dp[i - j * j] + 1,dp[i]);
}
}
cout << dp[N];
}
반례의 경우 12가 있다.
12의 경우 dp[12] 는 4가 아닌 3이다. 즉 가장 높은 제곱수로 나누는 것이 꼭 정답이 아니란 거다
틀린 답 : 12 = 3^2 + 1^2 + 1^2 + 1^2 = 4 =dp[9]+dp[3]
맞은 답 : 12 = 2^2 + 2^2 + 2^2 = 3 = dp[4]+dp[8]
처음에는 O(N^2) 방식으로 풀었는데 시간초과가 떴다
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
int main()
{
vector<int> dp(100001,9999);
dp[1] = 1;
int N;
cin >> N;
for (int i = 2; i <= N; i++)
{
for (int j = 1; j < i/2+1; j++)
{
if (i == j * j)
{
dp[i] = 1;
break;
}
dp[i] = min(dp[i], dp[i - j] + dp[j]);
}
}
cout << dp[N];
}
dp[12]
dp[11] + dp[1]
dp[10] + dp[2]
dp[9] + dp[3]
dp[8] + dp[4]
dp[7] + dp[5]
dp[6] + dp[6] 의 모든 케이스를 돌려 보았기 때문이다.
이런 방법이 아닌
dp[12]
dp[1^2]+dp[11]
dp[2^2]+dp[8]
dp[3^2]+dp[3] 이 정도의 케이스만을 봐도 된다
그 이유는 12 의 경우
12 = 1^2 + 1^2 + 1^2 + 1^2 + 1^2 + 1^2 + 1^2 + 1^2 + 1^2 + 1^2 + 1^2 + 1^2
12 = 2^2 + 2^2 + 2^2
12 = 3^2 + 1^2 + 1^2 + 1^2
이렇게 3가지의 케이스가 있다
여기서 dp[i] = min(dp[i - j * j] + 1,dp[i]); 식은 위에 부터 각각
1^2 , 2^2 , 3^2 를 빼준 것을 의미한다
즉 모든 케이스를 살펴볼 필요 없이 O(N^1.5) 의 시간복잡도로 단축시킬 수 있다.
'백준 c++ > (1-1)백준 c++ 알고리즘 기초' 카테고리의 다른 글
백준 2225 c++ 합분해 (0) | 2022.07.24 |
---|---|
백준 1149 c++ RGB거리 (0) | 2022.07.23 |
백준 1309 c++ 동물원 (0) | 2022.07.21 |
백준 15998 c++ 1, 2, 3 더하기 3 (0) | 2022.07.21 |
백준 1912 c++ 연속합 (0) | 2022.07.20 |