알고리즘/문제

(22.03.27) (Baekjoon) 12852. 1로 만들기 2

Job_E 2022. 3. 28. 23:27

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

 

12852번: 1로 만들기 2

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

www.acmicpc.net

3가지 연산을 이용하여 n을 1로 만드는 문제다.

 

  1. X가 3으로 나누어 떨어지면, 3으로 나눈다.
  2. X가 2로 나누어 떨어지면, 2로 나눈다.
  3. 1을 뺀다.

이 3가지 연산을 통해 입력받은 숫자를 1로 만들어야 한다.

그 과정 중 가장 빠르게 갈 수 있는 과정을 구해야 한다.(ex. 10의 경우, 10-9-3-1의 과정으로 1로 갈 수 있다.)

 

이걸 풀 수 있는 방법은 DP와 BFS가 있으며, 이 두가지를 모두 알아본다.

 

 

- 0. 공통

해당 문제에서는 가장 빠른 경로의 숫자 개수와 숫자 구성을 모두 가져와야 하기에,

두 값을 모두 저장하기 위해 static class Node를 사용한다.

static class Node {
		int n; // 경로 길이
		String str; // 경로 구성
		public Node(int n, String str) {
			super();
			this.n = n;
			this.str = str;
		}
	}

 

 

- 1. DP

우선 위에서 선언한 클래스를 자료형으로 가지는 1차원 배열을 만든다.

ar = new Node[n+1];
		for (int i = 0; i <= n; i++) {
			ar[i] = new Node(0,""); // 클래스 배열을 선언하면, 처음의 값은 null임을 알아두자.
		}

구하고자 하는 값은 ar[n].n과 ar[n].str임을 기억하고,

ar[1]의 경우는 반드시 포함되므로 기본 값으로써 각각 1과 "1"을 넣어준다.

ar[1].n = 1; // 1이라는 경로는 반드시 포함되어야 한다.
ar[1].str = Integer.toString(ar[1].n); // Integer값을 문자열로 변환한다. "1"로 넣어도 상관 X

이제 2부터 n까지 for문을 돌려서 각 숫자로 갈 수 있는 경로가 있는지 찾는 과정을 진행한다.

 

1- 1을 빼는 경우는 현재 수가 1보다 큰 한 반드시 추가될 수 있으므로 경로에 추가한다.

ar[i].n = ar[i-1].n+1;
ar[i].str = Integer.toString(i) + " " + ar[i-1].str; //경로를 기록할 때, 현재 만난 값이 앞에 오게 경로를 기록한다.

2- 2를 나누는 경우

n이 2로 나누어떨어질 때 시행하면 된다.

if(i % 2 == 0)
    if(ar[i].n > ar[i/2].n) {
        ar[i].n = ar[i/2].n+1;
        ar[i].str = Integer.toString(i) + " " + ar[i/2].str;
    }

 

3- 3을 나누는 경우

n이 3으로 나누어떨어질 때 시행하면 된다.

if(i % 3 == 0) {
    if(ar[i].n > ar[i/3].n) {
        ar[i].n = ar[i/3].n+1;
        ar[i].str = Integer.toString(i) + " " + ar[i/3].str;
    }
}

 

위 세가지 연산을 2~n 반복문 안에서 시행한다.

for (int i = 2; i <= n; i++) {
			ar[i].n = ar[i-1].n+1;
			ar[i].str = Integer.toString(i) + " " + ar[i-1].str;
			if(i % 2 == 0)
				if(ar[i].n > ar[i/2].n) {
					ar[i].n = ar[i/2].n+1;
					ar[i].str = Integer.toString(i) + " " + ar[i/2].str;
				}
			if(i % 3 == 0) {
				if(ar[i].n > ar[i/3].n) {
					ar[i].n = ar[i/3].n+1;
					ar[i].str = Integer.toString(i) + " " + ar[i/3].str;
				}
			}
		}

이때 의문점이라 하면,

위 방법으로 할 경우 결국 목표 숫자로 도착하는 경우가 여러가지인데 어떻게 최소인 경우를 고르느냐?

라는 게 생각되는데, 답은 계산의 순서에 있다.

+1만 해서 목표 숫자로 오게 되면 나머지 두 경우보다 많은 경로가 될 것이고, 

나머지 두 경로 중에서도 /3은 /2보다 짧은 경로로 목적지에 도착하게 된다.

 

따라서 위의 3가지 경로를 이동할 때 가장 짧은 경로로써 목표 값에 도달하게 되는 것이다.

 

더보기

전체 소스코드

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;

public class Main {
	static int n;
	static long ans;
	static Node[] ar;
	static Queue<Node> q = new LinkedList<Node>();
	public static void main(String[] args) throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		n = Integer.parseInt(br.readLine());
		ar = new Node[n+1];
		for (int i = 0; i <= n; i++) {
			ar[i] = new Node(0,"");
		}
		
		ar[1].n = 1;
		ar[1].str = Integer.toString(ar[1].n);
		for (int i = 2; i <= n; i++) {
			ar[i].n = ar[i-1].n+1;
			ar[i].str = Integer.toString(i) + " " + ar[i-1].str;
			if(i % 2 == 0)
				if(ar[i].n > ar[i/2].n) {
					ar[i].n = ar[i/2].n+1;
					ar[i].str = Integer.toString(i) + " " + ar[i/2].str;
				}
			if(i % 3 == 0) {
				if(ar[i].n > ar[i/3].n) {
					ar[i].n = ar[i/3].n+1;
					ar[i].str = Integer.toString(i) + " " + ar[i/3].str;
				}
			}
		}
		System.out.println(ar[n].n-1);
		System.out.println(ar[n].str);
	}

	static class Node {
		int n;
		String str;
		public Node(int n, String str) {
			super();
			this.n = n;
			this.str = str;
		}
		
	}
}