[코딩테스트] 다이나믹 프로그래밍(제곱수의 합부터 퇴사까지)

reference : 2021 코딩테스트 기초(최백준) 강의를 공부하며 정리한 내용입니다.

가장 긴 증가하는 부분 수열(1699번)

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

문제

  • 주어진 자연수 N을 제곱수들의 합으로 표현할 때 그 항의 최소개수를 구하는 문제

풀이

  • 1,2,3 더하기 문제와 매우 유사, 제곱수 사용해 구한 N의 최소 개수 구하면 됨
  • D[N] = N을 제곱수의 합으로 나타낼 때의 항의 최소 개수
  • i² 전의 합은 N-i² 이며 최소 개수는 D[N-i²]임
  • D[N] = min(D[N-i²]+1)
  • 1≤i²≤N 이니까 i≤√n과 같아서 하나의 값을 구하는데 걸리는 시간 √n 따라서 O(N x √n)
import java.util.*;

public class Main {
	public static void main(String args[]) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int[] d = new int[n+1];

		for(int i=1; i<=n; i++) {
			d[i] = i;
			for(int j=1; j*j<=i; j++) {
				if(d[i] > d[i-j*j]+1) {
					d[i] = d[i-j*j]+1;
				}
			}
		}
		System.out.println(d[n]);
	}
}

-> 풀이만 알면 코드를 짜는건 간단한데 아직 딱 문제만 보고 dp사고를 하는게 쉽지가 않다… 여러 문제를 풀어보자ㅠㅠㅠㅠㅠ

합분해(2225번)

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

문제

  • 0부터 N까지의 정수 K개를 더해서 그 합이 N이 되는 경우의 수

풀이

  • 1,2,3 더하기 문제와 유사
  • D[k][n] = 정수 k개를 더해 그 합이 n이 되는 경우의 수
  • 마지막엔 l이 온다고 했을 때, l이 없으면 앞쪽은 k-1개, 합은 n-l
  • D[k-1][n-l] + l = N, l의 범위는 0≤l≤n
  • D[k][n] = ∑ D[k-1][n-l]
  • 전체 문제 시간: kn, 하나 구하는데 시간: 0(N), O(kn²), dp에서 전체문제 개수는 Memoization 배열의 크기와 대부분 같다
  • 초기화 수 0개, 합 0개 d[0][0] = 1
import java.util.*;

public class Main {
	public static void main(String args[]) {
		Scanner sc = new Scanner(System.in);
		final long mod = 1000000000L;
		int n = sc.nextInt();
		int k = sc.nextInt();
		long[][] d = new long[k+1][n+1];
		d[0][0] = 1; //초기화
		for(int i=1; i<=k; i++) {
			for(int j=0; j<=n; j++) {
				for(int l=0; l<=j; l++) {
					d[i][j] += d[i-1][j-l];
					d[i][j] %= mod;
				}
			}
		}
		System.out.println(d[k][n]);
	}
}

퇴사(14501번)

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

문제

  • N+1일이 되는 날 퇴사를 하려 한다(1≤N≤15)
  • 남은 N일 동안 최대한 많은 상담 하려 함.
  • 하루에 하나의 상담 할 수 있고 i일에 상담 하면 T[i]일이 걸리고 P[i]원을 번다

풀이

  • 브루트 포스로 풀었던 문제, O(2²) 걸렸었음
  • 4일이 된 경우 전체 정답 (1~3일까지 상담한 수익)+(4~6일까지 상담한 수익), 즉 1~3일은 최대값만 알면 됨
  • 큰 문제의 정답을 작은 문제에서 구할 수 있고, 작은 문제 정답은 언제나 일정하기 때문에 dp를 이용해서 풀 수 있음
import java.util.*;

public class Main {
	static int n;
	static int[] t;
	static int[] p;
	static int[] d;
	static int ans = 0;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		t = new int[n+1];
		p = new int[n+1];
		d = new int[n+1];
		for (int i = 1; i <= n; i++) {
			t[i] = sc.nextInt();
			p[i] = sc.nextInt();
			d[i] = -1;
		}
		System.out.println(go(1));
	}

	private static int go(int day) {
		if(day==n+1) { //날짜가 퇴사일 됐을때
			return 0;
		}
		if(day>n+1) { //날짜가 퇴사일 넘었을때
			return -100000000;
		}
		if(d[day]!=-1) { //초기값이 아닐때 저장된 값 출력
			return d[day];
		}

		int t1 = go(day+1); //상담 안했을때
		int t2 = p[day] + go(day+t[day]); //상담 했을때
		d[day] = Math.max(t1, t2); //memorization 배열에 최대값 저장
		return d[day];
	}
}

-> 이거 브루트포스 재귀로 할때도 한참 걸렸었는데… dp방법 코드 이해하는게 더 어려운거 같다…ㅠㅠ dp 문제는 진짜 많이 풀고 익숙해져야 할거같다

댓글남기기