728x90
728x90
시간 제한 | 메모리 제한 | 제출 | 정답 | 맞힌 사람 | 정답 비율 |
0.15 초 (하단 참고) | 128 MB | 230137 | 75838 | 48572 | 32.294% |
문제
정수 X에 사용할 수 있는 연산은 다음과 같이 세 가지 이다.
- X가 3으로 나누어 떨어지면, 3으로 나눈다.
- X가 2로 나누어 떨어지면, 2로 나눈다.
- 1을 뺀다.
정수 N이 주어졌을 때, 위와 같은 연산 세 개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.
입력
첫째 줄에 1보다 크거나 같고, $10^{6}$보다 작거나 같은 정수 N이 주어진다.
출력
첫째 줄에 연산을 하는 횟수의 최솟값을 출력한다.
예제 입력 1
2
예제 출력 1
1
예제 입력 2
10
예제 출력 2
3
힌트
10의 경우에 10 -> 9 -> 3 -> 1 로 3번 만에 만들 수 있다.
알고리즘 분류
- 다이나믹 프로그래밍
시간 제한
- Python 3: 1.5 초
- PyPy3: 0.7 초
- Python 2: 1.5 초
- PyPy2: 0.7 초
문제 출처
https://www.acmicpc.net/problem/1463
문제 해결 방법
- 시간 제한 0.15초의 문제로, DP를 이용하여 빠르게 문제를 해결할 수 있다.
- @dp[n]@은 @n@을 1로 만들 수 있는 연산 횟수의 최솟값이라고 가정한다.
점화식 구하기
- 우선, 문제에 주어진 가능한 연산은 다음과 같다.
[연산 1] X가 3으로 나누어 떨어지면, 3으로 나눈다. [연산 2] X가 2로 나누어 떨어지면, 2로 나눈다. [연산 3] 1을 뺀다. |
- DP는 Bottom-Up 방식의 문제 해결 방법이므로, @n = 1@부터 @n = 3@까지 기본적인 값들을 미리 구해놓고 순차적으로 계산해본다.
dp[1] = 0; // X
dp[2] = 1; // [연산2]
dp[3] = 1; // [연산1]
예 : 1부터 10까지의 최소 연산 횟수 구하기
- 문제에서 주어진 연산을 이용하여 최소의 연산 횟수를 구할 수 있는 경우를 살펴보면 다음과 같다.
- @n = 1@일 때, @1@을 1로 만들 수 있는 최소의 연산은 없다.
- 따라서 @dp[1] = 0@이다.
- @n = 2@일 때, @2@를 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 2] (1가지)
- @dp[2] = 1@
- @n = 3@일 때, @3@을 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 1] (1가지)
- @dp[3] = 1@
- @n = 4@일 때, @4@를 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 2] + [연산 2] (2가지)
- [연산 3] + [연산 1] (2가지)
- @dp[4] = 2@
- @n = 5@일 때, @5@를 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 3] + [연산 3] + [연산 1] (3가지)
- [연산 3] + [연산 2] + [연산 2] (3가지)
- @dp[5] = 3@
- @n = 6@일 때, @6@을 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 1] + [연산 2] (2가지)
- [연산 2] + [연산 1] (2가지)
- @dp[6] = 2@
- @n = 7@일 때, @7@을 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 3] + [연산 1] + [연산 2] (3가지)
- [연산 3] + [연산 2] + [연산 1] (3가지)
- @dp[7] = 3@
- @n = 8@일 때, @8@을 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 2] + [연산 2] + [연산 2] (3가지)
- @dp[8] = 3@
- @n = 9@일 때, @9@를 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 1] + [연산 1] (2가지)
- [연산 3] + [연산 2] + [연산 2] + [연산 2] (4가지)
- @dp[9] = 2@
- @n = 10@일 때, @10@을 1로 만들 수 있는 최소의 연산은 다음과 같다.
- [연산 3] + [연산 1] + [연산 3] (3가지)
- [연산 2] + [연산 3] + [연산 2] + [연산2] (4가지)
- @dp[10] = 3@
- 표로 정리하면 다음과 같다.
n | dp[n] (최소 연산 횟수) |
1 | 0 |
2 | 1 |
3 | 1 |
4 | 2 |
5 | 3 |
6 | 2 |
7 | 3 |
8 | 3 |
9 | 2 |
10 | 3 |
- 따라서 @dp[n]@에 다음의 두 연산 중 최솟값을 선택해주면 된다..
① @dp[n - 1]@ + 1 ② @dp[i / 2] + 1@ or @dp[i / 3] + 1@ |
- 이를 토대로 점화식을 구하면 다음과 같다.
$$dp[n] = \begin{cases} 0 &\text{(if n = 1)} \\ \text{min}(dp[n], \; dp[n / 2] + 1) & (\text{if n % 2 = 0}) \\ \text{min}(dp[n], \; dp[n / 3] + 1) & (\text{if n % 3 = 0}) \\ dp[n - 1] + 1 & \text{Otherwise} \end{cases}$$
- 코드로 표현하면 다음과 같다.
int Solution(int n) {
dp[1] = 0;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + 1;
if (i % 2 == 0) { // 2로 나누어 떨어질 경우
dp[i] = min(dp[i], dp[i / 2] + 1);
}
if (i % 3 == 0) { // 3으로 나누어 떨어질 경우
dp[i] = min(dp[i], dp[i / 3] + 1);
}
}
return dp[n];
}
- 쉽게 이해해보자. @dp[4]@를 구한다고 해보자.
- @dp[4]@는 다음과 같이 2가지 방법으로 구할 수 있으며, 두 방법 중 최솟값을 선택해준다.
- ① 방법 1 : @dp[n] = dp[n - 1] + 1@
- @dp[4] = dp[4 - 1] + 1 = dp[3] + 1@
- @dp[3] = dp[4] - 1@
- 4를 구하는 최소 연산 횟수에서 연산 횟수 1을 빼주면 된다. (1을 빼준다. 즉, [연산 3] 수행)
- 3을 1로 만드는 최소 연산 횟수는 4를 1로 만드는 최소 연산 횟수에서 1을 빼주면 된다.
- dp[3] = 1 ([연산 1])
- dp[4] = 2 (
[연산 3]+ [연산 1])
- @dp[3] = dp[4] - 1@
- @dp[4] = dp[4 - 1] + 1 = dp[3] + 1@
- ② 방법 2 : @dp[n] = dp[n / 2(3)] + 1@
- @dp[4] = dp[4 / 2] + 1 = dp[2] + 1@ (2로 나누어 떨어지는 경우)
- @dp[2] = dp[4] - 1@
- 4를 구하는 최소 연산 횟수에서 연산 횟수 1을 빼준다.(2로 나눠준다. 즉, [연산 2] 수행)
- 2를 1로 만드는 최소 연산 횟수는 4를 1로 만드는 최소 연산 횟수에서 1을 빼주면 된다.
- dp[2] = 1 ([연산 2])
- dp[4] = 2 (
[연산 2]+ [연산 2])
- @dp[2] = dp[4] - 1@
- @dp[4] = dp[4 / 3] + 1@ (3으로 나누어 떨어지는 경우)
- @dp[4 / 3] = dp[4] - 1@
- 4를 구하는 최소 연산 횟수에서 연산 횟수 1을 빼준다. (3으로 나눠준다. 즉, [연산 1] 수행)
- @dp[4 / 3] = dp[4] - 1@
- @dp[4] = dp[4 / 2] + 1 = dp[2] + 1@ (2로 나누어 떨어지는 경우)
- ① 방법 1 : @dp[n] = dp[n - 1] + 1@
- 즉, @n@이 @2@ 또는 @3@으로 나누어 떨어지면 @방법 ①@과 @방법 ②@를 모두 수행해야 하므로, @dp[n - 1] + 1@ 과 @dp[n / 2] - 1@ (또는 @dp[n / 3] + 1@)의 최솟값을 @dp[n]@으로 지정한다.
- 그렇지 않을 경우, @방법 ②@를 수행할 필요가 없으므로, @dp[n - 1] + 1@을 @dp[n]@으로 지정한다.
코드
#include <iostream>
#include <cmath>
using namespace std;
int N;
int dp[1000001];
void Input() {
cin >> N;
}
int Solution(int n) {
dp[1] = 0;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + 1;
if (i % 2 == 0) { // 2로 나누어 떨어질 경우
dp[i] = min(dp[i], dp[i / 2] + 1);
}
if (i % 3 == 0) { // 3으로 나누어 떨어질 경우
dp[i] = min(dp[i], dp[i / 3] + 1);
}
}
return dp[n];
}
void Output() {
cout << Solution(N) << '\n';
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
Input();
Output();
return 0;
}
채점 결과
참고
- [단계별로 풀어보기] > [동적 계획법 1]
- 실버III
728x90
728x90
'Problem Solving > BOJ' 카테고리의 다른 글
[BOJ-16499][C++] 동일한 단어 그룹화하기 (2) | 2022.12.14 |
---|---|
[BOJ-11053][C++] 가장 긴 증가하는 부분 수열 (0) | 2022.12.14 |
[BOJ-2156][C++] 포도주 시식 (0) | 2022.12.13 |
[BOJ-10844][C++] 쉬운 계단 수 (0) | 2022.12.12 |
[BOJ-17427][C++] 약수의 합 2 (0) | 2022.12.11 |
[BOJ-2579][C++] 계단 오르기 (0) | 2022.12.09 |
[BOJ-1932][C++] 정수 삼각형 (0) | 2022.12.08 |
[BOJ-1149][C++] RGB거리 (0) | 2022.12.08 |