728x90

82번 사전


내가 떠올린 풀이 해설

a와 z의 개수가 각각 N, M개일 때 이 문자들로 만들 수 있는 모든 경우의 수는 N + M 개에서 N개를 뽑는 경우의 수 또는 N + M개에서 M개를 뽑는 경우의 수와 동일하다. arr배열을 초기화하고, 점화식으로 값을 계산해 저장한다. 몇 번째 문자열을 표현해야 하는지를 나타내는 변수를 K라고 하자 현재 자릿수에서 a를 선택했을 때 남아 있는 문자들로 만들 수 있는 모든 경우의 수는 T이다. T와 K를 비교해 문자를 선택한다. 문자 선택 기준은 T >= K : 현재 자리 문자를 a로 선택 T < K : 현재 자리 문자를 Z로 선택하고, K = K - T로 업데이트 

ex) a = 2, z = 2이고 a를 선택했을 때 나머지 문자열로 만들 수 있는 경우의 수는 arr[남은문자 총 개수 : 3][남은 z개수 : 2]

arr [3][2] = 3 >= k(2)이므로 a 확정 -> z는 2개 남음

arr [3][2] = 3 < k(2)이므로 z 확정 -> z는 1개 남음 -> K = K - T = 1로 업데이트

arr [1][1] = 1 >= k(1)이므로 a 확정 -> z는 1개 남음

arr [0][1] = 1 < k(2)이므로 z 확정


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek1256 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		int k = Integer.parseInt(st.nextToken());
		int [][] arr = new int[202][202];
		for(int i = 0; i <= 200; i++) {  // 조합 테이블 초기화 
			for(int j = 0; j <= i; j++) {
				if(j == 0 || j == i) {
					arr[i][j] = 1;
				}
				else {
					arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j];
					if(arr[i][j] > 1000000000) { //k 범위가 넘어가면 범위의 최댓값 
						arr[i][j] = 1000000001;
					}
				}
			}
		}
		if(arr[n + m][m] < k) { // 주어진 자릿수로 만들 수 없는 K번째 수이면 
			System.out.println(-1);
		}
		else {
			// a를 선택했을 때 남은 문자로 만들 수 있는 모든 경우의 수가 K 보다 크면 
			while(!(n == 0 && m == 0)) {
				if(arr[n - 1 + m][m] >= k) {
					System.out.print("a");
					n--;
				}
				else { // 모든 경우의 수가 k보다 작으면 
					System.out.print("z");
					k = k - arr[n - 1 + m][m]; // k값 업데이트 
					m--;
				}
			}
		}
	}
}

오늘의 회고

오늘은 캐치 콘에서 하는 세미나 참가로 조합 문제 1문제를 풀었습니다. 이제 조합 문제 1문제가 남았고 이제 DP를 들어갈 차례입니다. DP까지 풀면 중요한 알고리즘은 다 풀었습니다. 개념 복습을 하면서 다시 문제 풀고 문제 복습을 하는 순서로 공부를 해야겠습니다.

728x90
728x90

80번 조약돌 꺼내기


내가 떠올린 풀이 해설

단순하게 확률로 풀었습니다. 조약돌 개수를 배열에 담아서 한 색깔의 조약돌만 뽑을 확률을 색깔별로 모두 구하고 각각의 확률을 더해 정답으로 출력했다.

ex) 5개 조약돌이 있는 색만 뽑을 확률 : 5/18 * 4/17


정확한 풀이 1

import java.io.*;
import java.util.*;

public class Baek13251 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int M = Integer.parseInt(br.readLine());
		
		int[] cnt = new int[M];
		double sum = 0.0;
		StringTokenizer st = new StringTokenizer(br.readLine());
		for(int i = 0; i < M; i++) {
			cnt[i] = Integer.parseInt(st.nextToken());
			sum += cnt[i];
		}
		int K = Integer.parseInt(br.readLine());
		
		double answer = 0.0;
		for(int i = 0; i < M; i++) {
			double max = 1.0;
			for(int j = 0; j < K; j++) {
				max *= (cnt[i] - j) / (double)(sum - j);
			}
			answer += max;
		}
		System.out.println(answer);
	}
}

81번 순열의 순서


내가 떠올린 풀이 해설

이 문제는 조합 문제와 다르게 순열의 개념을 알아야 풀 수 있다. 순열은 순서가 다르면 다른 경우의 수로 인정된다. N자리로 만들 수 있는 순열의 경우의 수를 구해야 한다는 것이 이 문제의 핵심이다. 먼저 각 자리에서 사용할 수 있는 경우의 수를 구한다. 각 자리에서 구한 경우의 수를 모두 곱하면 모든 경우의 수가 나온다. 4자리로 표현되는 모든 경우의 수는 4! = 24이다. N자리로 만들 수 있는 순열의 모든 경우의 수는 N!이다.

k번째 순열 출력하기

  1. 주어진 값(k)과 현재 자리(n) - 1에서 만들 수 있는 경우의 수를 비교한다.
  2. 1에서 k가 더 작아질 때까지 경우의 수를 배수(cnt)로 증가시킨다.(순열의 순서를 1씩 늘림)
  3. k가 더 작아지면 순열에 값을 저장하고 k를 k - (경우의 수 * (cnt - 1))로 업데이트한다.
  4. 순열이 완성될 때까지 1 ~ 3을 반복하고 완료된 순열을 출력한다.

입력된 순열 순서 구하기

  1. N자리 순열의 숫자를 받아 몇 번째 순서의 숫자인지 확인한다.(현재 숫자 - 자기보다 앞 숫자 중 이미 사용한 숫자)
  2. 해당 순서 * (현재 자리 - 1에서 만들 수 있는 순열의 개수)를 k에 더한다.
  3. 모든 자릿수에 관해 1 ~ 3을 반복한 후 K값을 출력한다.

정확한 풀이

import java.io.*;
import java.util.*;
public class Baek1722 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		
		ArrayList<Integer> arr[] = new ArrayList[n];
		
		StringTokenizer st = new StringTokenizer(br.readLine());
		int m = Integer.parseInt(st.nextToken());
		long f[] = new long[21];
		int[] s = new int[21];
		boolean visit[] = new boolean[21];
		f[0] = 1;
		for(int i = 1; i <= n; i++) {  // 팩토리얼 초기화 -> 각 자리수에서 만들 수 있는 경우의 수 
			f[i] = f[i - 1] * i;
		}
		
		if(m == 1) {
			long k = Long.parseLong(st.nextToken());
			for(int i = 1; i <= n; i++) {
				for(int j = 1, cnt = 1; j <= n; j++) {
					if(visit[j]) {
						continue;  // 이미 사용한 숫자는 사용할 수 없음 
					}
					if(k <= cnt * f[n - i]) { // 주어진 k에 따라 각 자리에 들어갈 수 있는 수 찾기 
						k -= ((cnt - 1) * f[n - i]);
						s[i] = j;
						visit[j] = true;
						break;
					}
					cnt++;
				}
			}
			for(int i = 1; i <= n; i++) {
				System.out.print(s[i] + " ");
			}
		}
		else {
			long k = 1;
			for(int i = 1; i <= n; i++) {
				s[i] = Integer.parseInt(st.nextToken());
				long cnt = 0;
				for(int j = 1; j < s[i]; j++) {
					if(visit[j] == false) {
						cnt++;   // 미사용 숫자 개수 만큼 카운트 
					}
				}
				k += cnt * f[n - i]; // 자릿수에 따라 순서 더하기 
				visit[s[i]] = true;
			}
			System.out.println(k);
		}
	}
}

오늘의 회고

오늘은 조합 중에 좀 어려운 문제 2문제를 풀었습니다. 두 번째 푼 문제는 아이디어를 떠올리는데 안 떠올라서 풀지 못했습니다. 알고리즘 문제를 잘 풀고 싶습니다. 아직은 많이 부족한 것 같습니다. 열심히 나아가겠습니다.

728x90
728x90

조합

조합은 n개의 숫자에서 r개를 뽑는 경우의 수를 뜻한다. 조합과 비교되는 순열은 n개의 숫자 중 r개를 뽑아 순서를 고려해 나열할 경우의 수를 이야기한다. 순열과 조합의 차이는 순서의 고려 유무이다. 조합은 실제 알고리즘 코딩 테스트에서 순열보다 조합의 출제 빈도수가 높고, 응용할 문제도 많다. 조합은 동적 계획법의 시작이라고 볼 수 있다. 따라서 알고리즘에서 조합을 구현할 때는 수학 공식을 코드화하지 않고 점화식을 사용해 표현한다.

 

조합의 점화식

1. 특정 문제를 가정하기

   5개 데이터에서 3개를 선택하는 조합의 경우의 수를 푸는 문제로 가정

2. 모든 부분 문제가 해결된 상황이라고 가정하고 지금 문제 생각하기

   먼저 5개의 데이터 중 4개를 이미 선택이 완료된 데이터라고 가정한다. 그리고 마지막 데이터의 선택 여부에 따른 경우의 수를 계산한다. 만약 마지막 데이터를 포함해 총 3개의 데이터를 선택하려면 선택이 완료됐다고 가정한 4개의 데이터에서 2개를 선택해야 된다. 마지막 데이터를 포함하지 않고 총 3개의 데이터를 선택하려면 이전 데이터 4개 중 3개를 선택해야 한다. 2가지 경우의 수를 합치면 데이터 5개 중 3개를 선택하는 경우의 수가 나온다.

 

5개 중 3개를 선택하는 경우의 수 점화식

D[5][3] = D[4][2] + D[4][3]

조합 점화식

D[i][j] = D[i - 1][j] + D[i - 1][j - 1]

점화식이 간단하므로 외울 수도 있지만, 원리를 정확하게 이해하는 것이 문제 응용하기 유리하다.

728x90

'알고리즘 > 알고리즘 이론' 카테고리의 다른 글

최소 신장 트리  (0) 2022.06.23
플로이드-위셜  (0) 2022.06.23
벨만 - 포드  (0) 2022.06.21
다익스트라  (0) 2022.06.21
위상 정렬  (0) 2022.06.21
728x90

78번 부녀회장이 될 테야


내가 떠올린 풀이 해설

a층의 b호에 살려면 자신의 아래층(a - 1)의 1호부터 b호까지 사람들의 수의 합만큼 사람들을 데려와 살아야 한다를 보고 점화식을 세워야 한다. 0층의 사람들은 i호에 i명이 살고 있다고 한다. arr[0][r] = r 모든 층의 1호는 0층 1호부터 1명이어서 모든 층이 1명이다. arr[j][1] = 1 a - 1층의 1호부터 b호까지의 점화식은 arr[j][r] = arr[j][r - 1] + arr[j - 1][r]이다. 


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek2775 {
	static int k, n;
	static int[][] arr;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int T = Integer.parseInt(br.readLine());
		for(int i = 0; i < T; i++) {
			k = Integer.parseInt(br.readLine());
			n = Integer.parseInt(br.readLine());
			arr = new int[15][15];
			for(int j = 1; j < 15; j++) {
				for(int r = 1; r < 15; r++) {
					arr[0][r] = r;
					arr[j][1] = 1;
					arr[j][r] = arr[j - 1][r] + arr[j][r - 1];
				}
			}
			System.out.println(arr[k][n]);
		}
	}
}

79번 다리 놓기


내가 떠올린 풀이 해설

이 문제는 M개의 사이트에서 N개를 선택하는 문제로 변경할 수 있다. 겹치지 않게 하려면 동쪽에서 N개를 선택한 후 서쪽과 동쪽의 가장 위쪽의 사이트에서부터 차례대로 연결할 수 밖에 없기 때문이다. 조합 문제로 변형해 풀 수 있다. 조합 점화식은 많은 문제에서 응용되기 때문에 꼭 알고 있어야 한다.


정확한 풀이

import java.io.*;
import java.util.*;
public class Baek1010 {
	static long[][] arr;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		arr = new long[31][31];
		for(int i = 0; i <= 30; i++) {
			arr[i][0] = 1;
			arr[i][i] = 1;
			arr[i][1] = i;
		}
		for(int i = 2; i <= 30; i++) {
			for(int j = 1; j < i; j++) {  // 고르는 수 개수가 전체 개수를 넘을 수 없다 .
				arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j]; // 조합 점화식 
			}
		}
		int T = Integer.parseInt(br.readLine());
		for(int i = 0; i < T; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			int n = Integer.parseInt(st.nextToken());
			int m = Integer.parseInt(st.nextToken());
			System.out.println(arr[m][n]);
		}
	}
}

오늘의 회고

오늘은 조합 문제 2문제를 풀었습니다. 어렵지 않고 기본 문제였고 점화식을 세우는 방법에 대해 학습할 수 있었습니다. 조합 점화식은 꼭 이해하고 암기가 되어 있어야할 것 같습니다. 장마라 습하고 눅눅해서 불쾌지수가 높아지는데 다들 파이팅입니다.

728x90
728x90

76번 이항 계수 1


내가 떠올린 풀이 해설

조합에서 가장 기본이 되는 문제이다. 일반 점화식을 이용하면 쉽게 해결할 수 있는 문제이다. n과 k를 입력받고 DP 배열을 선언한다. 배열은 arr [n + 1][n + 1] 그리고 DP 배열의 값을 다음과 같이 초기화한다. arr [i][1] = i -> i 개 중 1개를 뽑는 경우의 수는 i개 arr[i][0] = 1 -> i 개 중 1개도 선택하지 않는 경우의 수는 1개 arr[i][i] = i -> i 개 중 i 개를 선택하는 경우의 수는 1개이다. 점화식으로 DP 배열의 값을 채운다. arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1] 마지막으로 arr [n][k] 값을 출력한다.


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek11050 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int n = Integer.parseInt(st.nextToken());
		int k = Integer.parseInt(st.nextToken());
		int[][] arr = new int[n + 1][n + 1];
		for(int i = 0; i <= n; i++) {
			arr[i][0] = 1; // i 개에서 1개도 선택하지 않는 경우의 수는 0개 
			arr[i][i] = 1; // i 개에서 모두 선택하는 경우의 수는 1개 
			arr[i][1] = i; // i 개에서 1개 선택 경우의 수는 i개 
		}
		for(int i = 2; i <= n; i++) {
			for(int j = 1; j < i; j++) { // 고르는 수의 개수가 전체 개수를 넘을 수 없음 
				arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j]; // 조합 점화식 
			}
		}
		System.out.println(arr[n][k]);
	}
}


내가 떠올린 풀이 해설

바로 앞에 문제와 비슷한 문제이다 n의 값이 커지고 결괏값을 10,007로 나눈 나머지를 출력하라는 요구사항이 있다. 모듈러 연산의 특성을 이용해 문제를 풀었다. 모듈러 연산은 덧셈에 관해 위와 같이 각각 모듈러를 하고, 모듈러 연산을 수행한 것과 두 수를 더한 후 수행한 것의 값이 동일하므로 이 문제에서 arr배열에 결괏값이 나올 때마다 모듈러 연산을 수행하는 로직을 추가하면 문제를 해결할 수 있다.

 

모듈러 연산의 특성

(A mod N + B mod N) mod N = (A + B) mod N


정확한 풀이

import java.io.*;
import java.util.*;

public class Baek11051 {

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		
		int n = Integer.parseInt(st.nextToken());
		int k = Integer.parseInt(st.nextToken());
		int[][] arr = new int[n + 1][n + 1];
		int pow = 10007;
		for(int i = 0; i <= n; i++) {
			arr[i][0] = 1;
			arr[i][i] = 1;
			arr[i][1] = i;
		}
		for(int i = 2; i <= n; i++) {
			for(int j = 1; j < i; j++) {
				arr[i][j] = (arr[i - 1][j - 1] + arr[i -1][j]) % pow;
			}
		}
		System.out.println(arr[n][k]);
	}
}

오늘의 회고

오늘은 DP 문제를 풀기 위해 먼저 학습해야 되는 조합에 대해서 학습하고 조합에 관련된 기본 문제 2문제를 풀었습니다. DP가 알고리즘 문제 풀이에서 2번째로 중요하다고 들었습니다. 매번 출제되는 문제인 만큼 확실하게 학습하고 넘어가겠습니다.

 

728x90

+ Recent posts