[문제]

동호와 규완이는 212호에서 문자열에 대해 공부하고 있다. 김진영 조교는 동호와 규완이에게 특별 과제를 주었다. 특별 과제는 특별한 문자열로 이루어 진 사전을 만드는 것이다. 사전에 수록되어 있는 모든 문자열은 N개의 "a"와 M개의 "z"로 이루어져 있다. 그리고 다른 문자는 없다. 사전에는 알파벳 순서대로 수록되어 있다.

규완이는 사전을 완성했지만, 동호는 사전을 완성하지 못했다. 동호는 자신의 과제를 끝내기 위해서 규완이의 사전을 몰래 참조하기로 했다. 동호는 규완이가 자리를 비운 사이에 몰래 사전을 보려고 하기 때문에, 문자열 하나만 찾을 여유밖에 없다.

N과 M이 주어졌을 때, 규완이의 사전에서 K번째 문자열이 무엇인지 구하는 프로그램을 작성하시오.

[입력]

첫째 줄에 N, M, K가 순서대로 주어진다. N과 M은 100보다 작거나 같은 자연수이고, K는 1,000,000,000보다 작거나 같은 자연수이다.

[출력]

첫째 줄에 규완이의 사전에서 K번째 문자열을 출력한다. 만약 규완이의 사전에 수록되어 있는 문자열의 개수가 K보다 작으면 -1을 출력한다.

[예제]

 


>문제 풀이

N개의 "a"와 M개의 "z"으로 만들수 있는 문자열을 사전순으로 정렬했을 때 K번째 문자열을 구하라.

N과 M이 100 이하의 자연수이므로 N과 M으로 조합 가능한 문자는 (200!)/(100!*100!) 개 이다.

즉, 이걸 하나씩 다 조합해보고 k번째 문자열을 구하기에는 시간도 메모리도 너무 비효율적이다.

그럼 어떻게 풀어야 할까?

사전순으로 a가 먼저이고 z가 다음이다. 즉, 조합할 때 순서가 있다는 것.

만약 a 3개와 z 3개로 만들 수 있는 문자열에 대해서 살펴본다고 했을 때

맨 앞글자가 a로 시작 + "a 2개와 z 3개로 만들 수 있는 문자열"

맨 앞글자가 z로 시작 + "a 3개와 z 2개로 만들 수 있는 문자열"

이렇게 될 것이다.

즉, dp[N][M]를 "a" N개와 "z" M개로 만들 수 있는 문자열의 개수 라고 할 때

dp[N][M]= dp[N-1][M] + dp[N][M-1] 의 점화식을 만들 수 있다.

** 여기까지는 금방 생각해 냈는데, 구현에서 막혔다.**

그래서 다른 분의 코드를 참고하였다.

참고한 블로그: https://velog.io/@sungjin0757/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%80-1256%EB%B2%88-%EC%82%AC%EC%A0%84JAVA

 

[알고리즘] - 백준 1256번 : 사전(JAVA)

문제 풀러가기dp 문제를 풀이하는 방식으로 접근을 하여야 하지만, 좀 더 알아야 할 것이 있었습니다!n개의 문자와 m개의 문자를 조합하여 만들 수 있는 문자열의 개수를 구할 수 있는 것이 시급

velog.io


먼저 함수를 두 가지로 나눈다.

check 함수와 makeS함수

함수 1) check(int a, int z) //a는 "a"의 개수, z: "z"의 개수

"a" a개와 "z" z개로 만들 수 있는 문자열 개수를 구하는 함수이다.

함수 2) makeS(int a, int z, int k) //k는 구하고자하는 문자열의 순서

"a" a개와 "z" z개로 만들수 있는 문자열들 중 k번째 문자열을 구하는 함수

<main>

//"a" N개와 "z" M개로 만들 수 있는 문자열의 개수가 K보다 작으면 -1출력

if( check(N, M) < K ) 일 때 -1출력

else{

   makeS(N, M, K);

   문자열 출력;

}


check 함수는 점화식을 그대로 구현한 것이라 아래 코드를 보면 어렵지 않은 것을 알 수 있다.

makeS 함수도 어렵지는 않다.

(* 문자는 하나씩("a"||"z") 붙여줘야 하기 때문에 StringBuilder를 사용했다.)

분기점 1)

a==0일 때는 남은 a가 없으니까 뒤로는 z로만 채우면 되고

z==0일 때는 남은 z가 없으니까 뒤로는 a로만 채우면 된다.

분기점 2)

// a로 시작하게 됐을 때 만들 수 있는 문자열의 개수를 구해본다.

double check= check(a-1, z);

if ( check < k ) {

     // 이면, a로 시작하게 되면 k번째 문자열을 포함하는 문자열을 못만든다는 것이다.

     // 그러면 "z"를 붙여줘야하고

     // makeS(a, z-1, k-check);

     // 왜?? a는 안썼고, z는 하나 썼으며

     // k에서 check를 빼주는 이유: z로 시작함으로써 a로 시작하는 문자열들의 개수는 빼고 넘겨주는 것

}else{

     // check>=k 이면, a로 시작했을 때 k번째 문자열을 구할 수 있다.

     // "a"를 붙여주고

     // makeS(a-1, z, k);

     // 왜?? a 하나 썼고, z는 안썼다.

     //그리고 a로 시작 했을 때 k번째 문자열을 구할 수 있으니까 그대로 넘겨준다.

}

 

>전체 코드

 

import java.util.Scanner;

public class Main {
	static double[][] dp= new double[101][101];
	static StringBuilder sb= new StringBuilder(); 
	
	public static void main(String[] args) {
		Scanner scan= new Scanner(System.in);
		
		//N은 a개수, M은 z개수, K번째 문자열을 구해야함.
		//N, M은 100이하, K는 10억 이하의 수
		int N= scan.nextInt();
		int M= scan.nextInt();
		double K= scan.nextDouble();
		
		if(check(N, M)<K) {
			System.out.println("-1");
		}else {
			makeS(N, M, K);
			System.out.println(sb.toString());
		}
	}//main
	
	//개수 구하는 함수
	public static double check(int a, int z) {
		if(a==0||z==0) return 1;
		if(dp[a][z]!=0) return dp[a][z];
		
		return dp[a][z]= Double.min(check(a-1, z)+check(a, z-1), 1000000001);
	}
	
	//문자열 만드는 함수
	public static void makeS(int a, int z, double k) {
		if(a==0) {
			for(int i=0; i<z; i++)
				sb.append("z");
			return;
		}
		if(z==0) {
			for(int i=0; i<a; i++)
				sb.append("a");
			return;
		}
		
		double check= check(a-1, z);
		if(k>check) {
			sb.append("z");
			makeS(a, z-1, k-check);
		}else {
			sb.append("a");
			makeS(a-1, z, k);
		}
	}//makeS	
}

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

 

1256번: 사전

첫째 줄에 N, M, K가 순서대로 주어진다. N과 M은 100보다 작거나 같은 자연수이고, K는 1,000,000,000보다 작거나 같은 자연수이다.

www.acmicpc.net

 

[문제]

우리는 스마트폰을 사용하면서 여러 가지 앱(App)을 실행하게 된다. 대개의 경우 화면에 보이는 ‘실행 중’인 앱은 하나뿐이지만 보이지 않는 상태로 많은 앱이 '활성화'되어 있다. 앱들이 활성화 되어 있다는 것은 화면에 보이지 않더라도 메인 메모리에 직전의 상태가 기록되어 있는 것을 말한다. 현재 실행 중이 아니더라도 이렇게 메모리에 남겨두는 이유는 사용자가 이전에 실행하던 앱을 다시 불러올 때에 직전의 상태를 메인 메모리로부터 읽어 들여 실행 준비를 빠르게 마치기 위해서이다.

하지만 스마트폰의 메모리는 제한적이기 때문에 한번이라도 실행했던 모든 앱을 활성화된 채로 메인 메모리에 남겨두다 보면 메모리 부족 상태가 오기 쉽다. 새로운 앱을 실행시키기 위해 필요한 메모리가 부족해지면 스마트폰의 운영체제는 활성화 되어 있는 앱들 중 몇 개를 선택하여 메모리로부터 삭제하는 수밖에 없다. 이러한 과정을 앱의 ‘비활성화’라고 한다.

메모리 부족 상황에서 활성화 되어 있는 앱들을 무작위로 필요한 메모리만큼 비활성화 하는 것은 좋은 방법이 아니다. 비활성화된 앱들을 재실행할 경우 그만큼 시간이 더 필요하기 때문이다. 여러분은 이러한 앱의 비활성화 문제를 스마트하게 해결하기 위한 프로그램을 작성해야 한다

현재 N개의 앱, A1, ..., AN이 활성화 되어 있다고 가정하자. 이들 앱 Ai는 각각 mi 바이트만큼의 메모리를 사용하고 있다. 또한, 앱 Ai를 비활성화한 후에 다시 실행하고자 할 경우, 추가적으로 들어가는 비용(시간 등)을 수치화 한 것을 ci 라고 하자. 이러한 상황에서 사용자가 새로운 앱 B를 실행하고자 하여, 추가로 M 바이트의 메모리가 필요하다고 하자. 즉, 현재 활성화 되어 있는 앱 A1, ..., AN 중에서 몇 개를 비활성화 하여 M 바이트 이상의 메모리를 추가로 확보해야 하는 것이다. 여러분은 그 중에서 비활성화 했을 경우의 비용 ci의 합을 최소화하여 필요한 메모리 M 바이트를 확보하는 방법을 찾아야 한다.

 

[입력]

입력은 3줄로 이루어져 있다. 첫 줄에는 정수 N과 M이 공백문자로 구분되어 주어지며, 둘째 줄과 셋째 줄에는 각각 N개의 정수가 공백문자로 구분되어 주어진다. 둘째 줄의 N개의 정수는 현재 활성화 되어 있는 앱 A1, ..., AN이 사용 중인 메모리의 바이트 수인 m1, ..., mN을 의미하며, 셋째 줄의 정수는 각 앱을 비활성화 했을 경우의 비용 c1, ..., cN을 의미한다

단, 1 ≤ N ≤ 100, 1 ≤ M ≤ 10,000,000이며, 1 ≤ m1, ..., mN ≤ 10,000,000을 만족한다. 또한, 0 ≤ c1, ..., cN ≤ 100이고, M ≤ m1 + m2 + ... + mN이다.

 

[출력]

필요한 메모리 M 바이트를 확보하기 위한 앱 비활성화의 최소의 비용을 계산하여 한 줄에 출력해야 한다.

 

[예제]


>문제 풀이

 유명한 배낭 알고리즘을 이용해서 풀어야하는 문제였습니다.

* 변수 설명

N // 입력되는 앱의 개수

M //필요한 메모리의 양

App apps[]; //앱을 저장할 객체 배열 App(int memory, int cost)

int dp[]; //dp[i]: 비용 i를 사용할때 비울 수 있는 최대 메모리

* 로직

for ( int i: 0~N ) {
      for( int j: costSum~apps[i].cost ){
            dp[j]= Math.max( dp[j], dp[j-apps[i].cost]+apps[i].mem );
      }
}

cost로 만들 수 있는 최대 메모리를 매번 구할 수 없기 때문에 dp 배열에 memoization해줍니다.

mem / cost(→) j-i j
dp[cost]
:cost로 만들수 있는 최대 메모리
dp[j-i] dp[j]=??

dp[j]= Math.max( dp[j], dp[j-apps[i].cost] + apps[i].mem );

dp[j]에 dp[j] 값이랑 ( j비용에서 i번째 앱의 비용을 뺀 비용으로 만들 수 있는 최대 메모리) + i번째 앱의 메모리큰 값을 넣습니다.

 

>전체 코드

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.io.IOException;

public class Main {
	
	public static void main(String [] args) throws IOException {
		int N, M, costSum=0;   //N: input 개수, M: 원하는 메모리값
		App apps[]; //input
		int dp[];   //cost가 i일때 최대 메모리
		
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st= new StringTokenizer(br.readLine());
		StringBuilder sb= new StringBuilder();
		
        //1. 입력받기
		N= Integer.parseInt(st.nextToken());
		M= Integer.parseInt(st.nextToken());
		apps=new App[N];
		//1-1. set Mem
		st= new StringTokenizer(br.readLine());
		for(int i=0; i<N; i++) {
			apps[i]= new App(0, 0);
			apps[i].setMemory(Integer.parseInt(st.nextToken()));
		}
		//1-2. set Cost
		st= new StringTokenizer(br.readLine());
		for(int i=0; i<N; i++) {
			apps[i].setCost(Integer.parseInt(st.nextToken()));
			costSum+= apps[i].getCost();
		}
		
		dp= new int[costSum+1];
		
		//2. napsack 알고리즘
		for(int i=0; i<N; i++) {
			for(int j=costSum; j>=apps[i].getCost(); j--) {
				dp[j]= Math.max(dp[j], dp[j-apps[i].getCost()]+apps[i].getMemory());
			}
		}
		
		//3. M이상 메모리를 확보하기 위한 비용 i 출력
		for(int i=0; i<=costSum; i++) {
			if(dp[i]>=M) {
				System.out.println(i);
				break;
			}
		}
	}
	
	static class App{
		int memory;
		int cost;
		public App(int memory, int cost) {
			this.memory = memory;
			this.cost = cost;
		}
		public int getMemory() {
			return memory;
		}
		public void setMemory(int memory) {
			this.memory = memory;
		}
		public int getCost() {
			return cost;
		}
		public void setCost(int cost) {
			this.cost = cost;
		}
	}//class App
}

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

 

7579번: 앱

입력은 3줄로 이루어져 있다. 첫 줄에는 정수 N과 M이 공백문자로 구분되어 주어지며, 둘째 줄과 셋째 줄에는 각각 N개의 정수가 공백문자로 구분되어 주어진다. 둘째 줄의 N개의 정수는 현재 활

www.acmicpc.net

 

[문제]

수 N개가 주어졌을 때, i번째 수부터 j번째 수까지 합을 구하는 프로그램을 작성하시오.

 

[입력]

첫째 줄에 수의 개수 N과 합을 구해야 하는 횟수 M이 주어진다. 둘째 줄에는 N개의 수가 주어진다. 수는 1,000보다 작거나 같은 자연수이다. 셋째 줄부터 M개의 줄에는 합을 구해야 하는 구간 i와 j가 주어진다.

 

[출력]

총 M개의 줄에 입력으로 주어진 i번째 수부터 j번째 수까지 합을 출력한다.

 

[제한사항]

1 ≤ N ≤ 100,000

1 ≤ M ≤ 100,000

1 ≤ i ≤ j ≤ N

[예제]

예제 입력1 예제 입력2
5 3
5 4 3 2 1
1 3
2 4
5 5
12
9
1

 


>문제 풀이

시간 복잡도를 먼저 살펴보겠습니다.

입력 N개에 대한 구간합을 구하려면 O(N)

구간이 M개가 입력되면 O(N*M)

입력되는 N과 M의 범위 1~100,000

그러면 백억...!ㄷㄷ

>>그래서 이렇게 풀면 안됩니다.

이 문제를 해결하기 위해 dp를 사용할건데, dp[N+1]배열에 구간합을 저장할 것입니다.

dp[k]= arr[1]~arr[k]까지 저장,

이렇게 되면 i~j까지의 구간합을 구하려면 dp[j]-dp[i-1]을 계산해주면 됩니다.

시간복잡도는 한번만 계산해서 저장해놓고 사용하면 되기 때문에 O(N)

ex) a~b 구간합을 구한다고 할 때, = dp[b] - dp[a-1];

 

>전체 코드

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.StringTokenizer;

public class Main {
	
	public static void main(String[] args) throws IOException {
		BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st= new StringTokenizer(br.readLine());
		
		//1. 입력
		int N= Integer.parseInt(st.nextToken());
		int M= Integer.parseInt(st.nextToken());
		int[] arr=new int[N+1];
		int[] dp= new int[N+1];
		
		st= new StringTokenizer(br.readLine());
		for(int i=1; i<=N; i++) {
			arr[i]= Integer.parseInt(st.nextToken()); // arr배열 값을 입력받음과 동시에
			dp[i]=dp[i-1]+arr[i];  // dp[]에 누적합 저장
		}
		
		int a, b;
		StringBuilder sb= new StringBuilder();
		for(int i=0; i<M; i++) {
			st= new StringTokenizer(br.readLine());
			a= Integer.parseInt(st.nextToken());
			b= Integer.parseInt(st.nextToken());
			sb.append(dp[b]-dp[a-1]+"\n");
		}
		System.out.println(sb.toString());
	}//main	
}

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

 

11659번: 구간 합 구하기 4

첫째 줄에 수의 개수 N과 합을 구해야 하는 횟수 M이 주어진다. 둘째 줄에는 N개의 수가 주어진다. 수는 1,000보다 작거나 같은 자연수이다. 셋째 줄부터 M개의 줄에는 합을 구해야 하는 구간 i와 j

www.acmicpc.net

 

[문제]

사악한 암흑의 군주 이민혁은 드디어 마법 구슬을 손에 넣었고, 그 능력을 실험해보기 위해 근처의 티떱숲에 홍수를 일으키려고 한다. 이 숲에는 고슴도치가 한 마리 살고 있다. 고슴도치는 제일 친한 친구인 비버의 굴로 가능한 빨리 도망가 홍수를 피하려고 한다.

티떱숲의 지도는 R행 C열로 이루어져 있다. 비어있는 곳은 '.'로 표시되어 있고, 물이 차있는 지역은 '*', 돌은 'X'로 표시되어 있다. 비버의 굴은 'D'로, 고슴도치의 위치는 'S'로 나타내어져 있다.

매 분마다 고슴도치는 현재 있는 칸과 인접한 네 칸 중 하나로 이동할 수 있다. (위, 아래, 오른쪽, 왼쪽) 물도 매 분마다 비어있는 칸으로 확장한다. 물이 있는 칸과 인접해있는 비어있는 칸(적어도 한 변을 공유)은 물이 차게 된다. 물과 고슴도치는 돌을 통과할 수 없다. 또, 고슴도치는 물로 차있는 구역으로 이동할 수 없고, 물도 비버의 소굴로 이동할 수 없다.

티떱숲의 지도가 주어졌을 때, 고슴도치가 안전하게 비버의 굴로 이동하기 위해 필요한 최소 시간을 구하는 프로그램을 작성하시오.

고슴도치는 물이 찰 예정인 칸으로 이동할 수 없다. 즉, 다음 시간에 물이 찰 예정인 칸으로 고슴도치는 이동할 수 없다. 이동할 수 있으면 고슴도치가 물에 빠지기 때문이다.

[입력]

첫째 줄에 50보다 작거나 같은 자연수 R과 C가 주어진다.

다음 R개 줄에는 티떱숲의 지도가 주어지며, 문제에서 설명한 문자만 주어진다. 'D'와 'S'는 하나씩만 주어진다.

[출력]

첫째 줄에 고슴도치가 비버의 굴로 이동할 수 있는 가장 빠른 시간을 출력한다. 만약, 안전하게 비버의 굴로 이동할 수 없다면, "KAKTUS"를 출력한다.

​[예제]

 


>문제 풀이

D : 비버 굴

S : 고슴도치 위치

* : 물

. : 빈 곳

X : 돌

char mat[][]에 입력값들을 저장하면서, 물의 위치와 시작 위치를 큐에 담아줍니다.

물의 위치를 먼저 쭉 담고, 시작 위치를 담도록 구현했는데 물 먼저 bfs를 해주고 고슴도치가 움직여야 정답값을 효율적으로 구할 수 있기 때문입니다.

(고슴도치가 움직여도 그 다음에 물에 잠기면 처리가 좀 더 복잡해집니다.)

물이 상하좌우로 확장해나갈 때는 mat범위 내에서 발생하며 값도 *으로 변경해줍니다.

고슴도치는 빈 곳으로만 이동해서 D에 도달해야합니다. 최종적으로 D에 도달했을 때, 몇 분 걸렸는지를 출력해야하는데 dp[][]값에 시작 위치부터 카운트해서 최단거리로 D에 도달하는 시간을 구했습니다.

위의 과정을 다 돌렸는데도, D에 도달하지 못했다면 "KAKTUS"를 출력합니다.


저는 처음에 고슴도치가 먼저가고 물이 확장되도록 구현했는데, 이렇게 했더니 queue.size()만큼만 for문을 반복하게 하는데에서 어려움을 겪었습니다.

 

https://arinnh.tistory.com/40?category=934581 

 

[프로그래머스] level 2: 가장 먼 노드_Java

[문제 설명] n개의 노드가 있는 그래프가 있습니다. 각 노드는 1부터 n까지 번호가 적혀있습니다. 1번 노드에서 가장 멀리 떨어진 노드의 갯수를 구하려고 합니다. 가장 멀리 떨어진 노드란 최단

arinnh.tistory.com

이 문제를 풀었던 것처럼 풀어보려고 했는데, 잘 안되어서 코드를 수정했습니다.

 

 

>전체 코드

 

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
	static int R, C;
	static char[][] mat;
	static int[][] dp;
	static Queue<Point> queue;
	
	static int time=0;
	
	public static void main(String[] args) {
		Point start = null;
		String str="";
		
		Scanner scan= new Scanner(System.in);
		
		R= scan.nextInt();
		C= scan.nextInt();
		mat= new char[R][C];
		dp= new int[R][C];
		queue= new LinkedList<>();
		
		//빈 곳: . // 물: * // 돌: X //비버 굴: D // 고슴도치: S
		for(int i=0; i<R; i++) {
			str= scan.next();
			for(int j=0; j<C; j++) {
				mat[i][j]= str.charAt(j);
				if(mat[i][j]=='S') {
					start= new Point(i, j, 'S');
				}else if(mat[i][j]=='*') {
					queue.add(new Point(i, j, '*'));
				}
			}
		}
		queue.add(start);
		
		bfs();
	}//main
	
	
	static int[] dx= {-1, 0, 1, 0};
	static int[] dy= {0, -1, 0, 1};
	
	//고슴도치가 비버 굴로 가야함(순서:물->고슴도치)
	public static void bfs() {
		Point p;
		int nx, ny;
		
		while(!queue.isEmpty()) {
			p= queue.poll();
			if(p.type=='D') {
				System.out.println(dp[p.x][p.y]);
				return;
			}

			for(int i=0; i<4; i++) {
				nx= p.x+dx[i];
				ny= p.y+dy[i];

				if(!check(nx, ny)) continue;				
				if(p.type=='*') {
					if(mat[nx][ny]=='.'||mat[nx][ny]=='S') {
					mat[nx][ny]= '*';
					queue.add(new Point(nx, ny, '*'));
					}
				}else { //p.type=='.'
					if(mat[nx][ny]=='.'||mat[nx][ny]=='D') {
						if(dp[nx][ny]>0) continue;
						dp[nx][ny]= dp[p.x][p.y]+1;
						queue.add(new Point(nx, ny, mat[nx][ny]));
					}
				}
			}
		}//while
		System.out.println("KAKTUS");
	}

	//범위 확인
	public static boolean check(int x, int y) {
		if(x<0||x>=R) return false;
		if(y<0||y>=C) return false;
		return true;
	}
	
	static class Point{
		int x, y;
		char type;
		public Point(int x, int y, char type) {
			this.x= x;
			this.y= y;
			this.type= type;
		}
	}
}

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

 

3055번: 탈출

사악한 암흑의 군주 이민혁은 드디어 마법 구슬을 손에 넣었고, 그 능력을 실험해보기 위해 근처의 티떱숲에 홍수를 일으키려고 한다. 이 숲에는 고슴도치가 한 마리 살고 있다. 고슴도치는 제

www.acmicpc.net

 

[문제]

N!의 값을 계산한 후에, 0이 아닌 가장 낮은 자리 수를 구하시오.

예를 들어, 4! = 24 이기 때문에, 0이 아닌 가장 낮은 자리 수는 4이다. 또, 5! = 120이기 때문에, 0이 아닌 가장 낮은 자리 수는 2 이다.

[입력]

첫째 줄에 N이 주어진다. N은 20,000보다 작거나 같은 자연수 이다.

[출력]

첫째 줄에 N!의 0이 아닌 마지막 자리수를 출력한다.

[예제]


 

>문제 풀이

예전에 틀렸던 문제를 다시 풀어보았습니다.

실버라더니 어려웠던 문제...

가끔 참고하는 블로그인데, 이번에도 도움을 받았습니다.

설명을 정말 잘 해놓으셨어요..bb

참고한 블로그 링크: https://steady-coding.tistory.com/322

 

위의 블로그에 너무 잘 정리되어있기 때문에 이번 포스팅은 제가 이해한 내용과 흐름을 위주로 추후에 다시 보기 위해 작성하겠습니다.


먼저 이 문제는 N!에 대해 0을 제외한 마지막 자릿수를 구하는 문제입니다.

입력되는 N의 범위가 무려 20,000 까지기 때문에 무작정 for문을 돌리면 시간초과가 일어납니다.

그래서 이 문제는 dp로 풀게 되었는데, 아래의 로직을 읽어보다 보면 왜 dp로 풀었는지 이해가 될 것입니다.

그래서 factorial의 계산에 대해 생각해 볼 필요가 있는데요.

먼저 끝자리에 0이 생기는 조건에 대해 생각해보면, 10의 제곱수를 곱하게 될 경우 끝자리에 0이 붙게 됩니다. 즉 2와 5의 조합에 따라 0이 생긴다고 보면되죠.

15!

= (1 x 2 x 3 x 4 x 5) x (6 x 7 x 8 x 9 x 10) x (11 x 12 x 13 x 14 x 15)

5의 배수를 앞으로 모아줍니다.

= (5 x 10 x 15) x (1 x 2 x 3 x 4) x (6 x 7 x 8 x 9) x (11 x 12 x 13 x 14)

뒤에 형광펜으로 색칠된 부분의 digit은 모두 4인것을 알 수 있습니다.

왜냐면 끝자리 기준으로

1 x 3 = 3, 2 x 4 = 8 >> digit= 4

7 x 9 =63, 6 x 8 =48 >> digit= 4

따라서 끝자리가 1, 2, 3, 4인 묶음이든, 6, 7, 8, 9인 묶음이든 0이 아닌 마지막 수는 4가 나옵니다.

= (5 x 10 x 15) x (1 x 2 x 3 x 4) x (6 x 7 x 8 x 9) x (11 x 12 x 13 x 14)

= 5^3 x 6 x 2^2 x 2^2 x 2^2

이제 0을 만들어내는 10을 없애주기 위해 2와 5를 조합해줍니다.

= 10^3 x 6 x 2^3 //10은 앞서 말했듯 끝자리 수에 영향 없는 0만 만들어주기 때문에

= 6 x 2^3

= 3! x 2^3

∴ N에 대해서 위의 방식대로 정리하게 되면

= A! x 2^p 의 구조가 나오게 됩니다.

우리는 이제 이 구조로 끝자리를 구할 수 있습니다.

이제 dp를 어떻게 적용시킬지 알아봅시다.

위의 식을 좀 더 정리해볼건데요.

각 숫자의 끝자리를 dp[] 배열에 담는다고 했을 때, 3!의 끝자리는 dp[3]에 담겨있겠죠?

그리고 2의 제곱수 같은 경우에는 끝자리가 2^1=2, 2^2=4, 2^3= 8, 2^4=6 이 반복됩니다.

즉, (dp[3] x 8)%10 이 15!의 0이 아닌 마지막 자릿수 입니다.

그렇다면 17!은 어떻게 구할 수 있을까요?

17!= dp[15] x 16 x 17

= dp[15] x 6 x 7

즉, N보다 작은 5의 배수의(=temp) dp값을 기준으로 temp+1~ N까지의 수를 곱해주면서 %10을 해주면 구할 수 있습니다.

* 팩토리얼 계산해주는 사이트 링크 입니다. 참고하세요~

https://ko.numberempire.com/factorialcalculator.php

 

 

>전체 코드

 

import java.util.Scanner;

public class Main {
	
	public static void main(String [] args) {
		int N, temp, dp[];
		
		Scanner scan= new Scanner(System.in);
		
		N= scan.nextInt();
		dp= new int[N+1];
		
		//2^n digit: 2 4 6 8
		dp[1]= 1;
		dp[2]= 2;
		dp[3]= 6;
		dp[4]= 4;
		
		for(int i=5; i<=N; i++) {
			if(i%5==0) {
				temp= i/5;
				dp[i]= (dp[temp]*(int)Math.pow(2, temp%4))%10;
			}else{
				temp= (i/5)*5;
				dp[i]=dp[temp];
				for(int j=temp+1; j<=i; j++) {
					dp[i]=dp[i]*j%10;
				}
			}
		}//end for
		
		System.out.println(dp[N]);
	}	
}

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

 

2553번: 마지막 팩토리얼 수

첫째 줄에 N이 주어진다. N은 20,000보다 작거나 같은 자연수 이다.

www.acmicpc.net

 

+ Recent posts