백준 1463번

신형석·2022년 7월 25일
0

알고리즘 풀이

목록 보기
32/41

문제

정수 X에 사용할 수 있는 연산은 다음과 같이 세 가지 이다.

X가 3으로 나누어 떨어지면, 3으로 나눈다.
X가 2로 나누어 떨어지면, 2로 나눈다.
1을 뺀다.
정수 N이 주어졌을 때, 위와 같은 연산 세 개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.

입력

첫째 줄에 1보다 크거나 같고, 106보다 작거나 같은 정수 N이 주어진다.

출력

첫째 줄에 연산을 하는 횟수의 최솟값을 출력한다.


동적 프로그래밍을 사용하는 문제 중 하나이다. 동적 프로그래밍에는 우선 점화식을 구하는 것이 중요하므로, 점화식을 먼저 구해보았다.

우선, 2와 3일때는 한번의 연산만으로 1에 도달할 수 있으므로 1이 출력되어야 한다. 그래서, 배열에 1을 먼저 넣어두고, 2와 3이 입력되었을 경우 1을 출력하고 프로그램을 끝내도록 하였다. 그럼 4 이상부터는 어떤 연산을 거쳐야 할까?

4 -> 4 / 2 = 2 -> 2 / 2 = 1 or 4 - 1 = 3 -> 3 / 3 = 1 =>> 2번
5 -> 5 - 1 = 4 -> 4 / 2 = 2 -> 2 / 2 = 1 or 5 - 1 = 4 -> 4 - 1 = 3 -> 3 / 3 = 1 =>> 3번
6 -> 6 / 2 = 3 -> 3 / 3 = 1 or 6 / 3 = 2 -> 2 / 2 = 1 =>> 2번
7 -> 7 - 1 = 6 -> 6 / 2 = 3 -> 3 / 3 = 1 or 7 - 1 = 6 -> 6 / 3 = 2 -> 2 / 2 = 1 =>> 3번

위 예시들을 보고, 다음과 같은 점화식을 세울 수 있다:

  1. 만약 수가 2로 나뉘어진다면, 2로 나눈 몫이 가졌던 최솟값 + 1
  2. 만약 수가 3으로 나뉘어진다면, 3으로 나눈 몫이 가졌던 최솟값 + 1
  3. 2도 3으로도 나뉘어 떨어지지 않는다면, 입력받은 수 - 1이 가졌던 최솟값 + 1

가 우리가 원하는 답이 되겠다. 이 중 최솟값을 구하면 된다.

짤 때 주의해야 할 점은, 2와 3 모두 나뉘어 떨어지는 수가 입력되었을 경우를 잘 생각하고 짜야한다는 것, 그리고 2와 3으로 나누는 과정에서의 최솟값을 잘 구해줘야 한다는 것이다. 백준에서 질문 사이트에 올라와있던 반례는 642였다.

642는 다음과 같은 과정으로 나뉘어진다.

  1. 642 -> 321 -> 320 -> 160 -> 80 -> 40 -> 20 -> 10 -> 9 -> 3 -> 1 =>> 10번
  2. 642 -> 214 -> 107 -> 106 -> 53 -> 52 -> 26 -> 13 -> 12 -> 4 -> 3 -> 1 =>> 11번

보통 생각하기에는 3으로 나누는 것이 최적해에 가까워지는 방법이라고 생각할수도 있다. 하지만 이러한 반례가 존재하는 것처럼, 위의 3가지 경우의 수를 모두 비교해보는 것이 좋은 방법이다.

#include <stdio.h>
#define MAX 1000000

int arr[MAX];

int main(void) {
	arr[2] = 1; arr[3] = 1;
	int input;
	scanf("%d", &input);
	if (input == 2 || input == 3) {
		printf("%d\n", 1);
		return 0;
	}
	int num1_1 = 0, num1_2 = 0, num1, num2;
	for (int i = 4; i <= input; i++) {
		num1_1 = 0; num1_2 = 0; num1 = 0; num2 = 0;
		if (i % 2 == 0 && i % 3 != 0) { // 2의 배수이면서 3의 배수는 아닐 때
			num1 = arr[i / 2] + 1;
		}
		else if (i % 2 != 0 && i % 3 == 0) { // 3의 배수이면서 2의 배수는 아닐 때
			num1 = arr[i / 3] + 1;
		}
		else if (i % 2 == 0 && i % 3 == 0) { // 2의 배수이면서 3의 배수, 즉 6의 배수일 때
			num1 = arr[i / 2] + 1 < arr[i / 3] + 1 ? arr[i / 2] + 1 : arr[i / 3] + 1;
		}
		else {
			arr[i] = arr[i - 1] + 1;
			continue;
		}
		num2 = arr[i - 1] + 1;
		arr[i] = num1 < num2 ? num1 : num2;

	}
	printf("%d\n", arr[input]);
	return 0;
}

0개의 댓글