가장 먼저 고민해야 할 것은 자료구조의 형태입니다.
이 '연결된 꼬리' 때문에 일반적인 재귀나 순차적 DP를 그대로 적용하기 어렵습니다. 보통 이런 문제는 "케이스를 분리하여 선형으로 환원"하는 것이 정석입니다.
문제의 제약 조건을 봅시다. 입니다.
만약 인 경우, 조합(Combination)의 수는 천문학적인 숫자가 됩니다.
이는 일반적인 백트래킹( 혹은 지수 시간)으로는 절대 시간 내에 풀 수 없습니다. 따라서 중복된 하위 문제(Overlapping Subproblems)를 해결하는 DP가 필수입니다.
를 개의 색상 중에서 인접하지 않게 개를 고르는 경우의 수라고 정의합시다.
번째 색상을 기준으로 생각해보면, 두 가지 선택지가 있습니다.
번째 색상을 선택하지 않는 경우 ():
번째 색상을 선택하는 경우 ():
보통 원형 문제는 "첫 번째를 뽑았을 때"와 "안 뽑았을 때"로 나누어 DP를 두 번 돌리거나 복잡한 처리를 합니다. 하지만, 초기값(Base Case)을 원형의 정의에 맞게 잘 세팅하면 위 점화식을 그대로 사용할 수 있습니다.
dp[i][1] = idp[2][1] = 2)이 초기값 설정을 통해, 점화식은 까지 순차적으로 돌면서 원형의 제약 조건을 포함한 값을 계산해냅니다. (수학적으로 루카스 수열 등의 성질과 관련이 깊습니다.)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
class Main {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static int N, K;
static int[][] dp;
static final int MOD = 1_000_000_003;
public static void main(String[] args) throws IOException {
N = Integer.parseInt(br.readLine());
K = Integer.parseInt(br.readLine());
// 예외 처리: N개로 인접하지 않게 K개를 고를 수 있는 최대치는 N/2
if (K > N / 2) {
System.out.println(0);
return;
}
dp = new int[N + 1][K + 1];
// 초기값 설정
// 2개 중 1개를 고르는 경우의 수 = 2
dp[2][1] = 2;
for (int color = 3; color <= N; color++) {
// color개 중 1개를 고르는 경우 = color 가지
dp[color][1] = color;
for (int k = 2; k <= K; k++) {
// 점화식: (이번 색 안 고름) + (이번 색 고름, 바로 앞은 못 고름)
dp[color][k] = dp[color - 1][k] + dp[color - 2][k - 1];
dp[color][k] %= MOD; // 모듈러 연산 필수
}
}
System.out.println(dp[N][K]);
}
}
이 문제에서 가장 중요한 판단은 "백트래킹을 배제하는 것"이었습니다.
long 범위도 초과할 만큼 크다는 힌트입니다.dp[1][1]의 함정코드에서 반복문은 color = 3부터 시작합니다.
dp[1][1]은 초기화되지 않아 0입니다.dp[2][1]=2로 명확히 박아두고, 3부터 루프를 돌린 것이 ArrayIndexOutOfBounds 에러를 피하는 깔끔한 방법이 되었습니다.