문제 풀이에 들어가기에 앞서서 DP에 대해서 먼저 찾아봤다.
→ N이 커질 수록 기하급수적으로 비효율적인 프로그램이 됨.

→ Botton-Up방식은 말 그대로 초기조건을 기반으로 차곡차곡 데이터를 쌓아가 큰 문제의 결과를 도출하는 과정이다. 보통 반복문을 사용한다. 예를 들어 메모이제이션을 위한 배열 dp[]가 있었다면, dp[0] 부터 차례로 값을 채운다. Bottom-Up에 한하여 이러한 값 채우기를 본 떠 메모이제이션을 Tabulation이라 부르기도 한다.
→ Top-Down방식은 주로 재귀함수를 사용하며 dp[n]값을 찾기 위한 재귀 함수의 호출이 dp[0](또는 초기 조건까지) 내려간 다음 결과들이 재귀 함수에 맞물리며 재활용되는 방식이다.
💡메모이제이션(memoization)이란 입력값에 대한 결괏값을 저장해둠으로써 같은 입력값에 따른 함수의 실행이 중복해서 일어나지 않도록 해주는 기법이다. DP로 문제를 풀 때 자주 등장하며, 함수의 중복 호출을 방지해 효율을 높여준다.
추가적으로 분할정복도 DP와 함께 자주 언급된다고 한다. 두 기법은 모두 문제를 소문제로 나누어 해결한다는 공통점이 있지만, 분할정복은 하위 문제에서 중복이 일어나지 않는 방면, DP는 하위 문제에서 중복이 일어난다는 차이점이 있다.
용어 정리: Top-Down = 하향식(메모이제이션), Bottom-Up = 상향식(타뷸레이션)
| 구분 | Top-Down (하향식, Memoization) | Bottom-Up (상향식, Tabulation) |
|---|---|---|
| 아이디어 | 큰 문제에서 시작해 필요한 하위 문제만 재귀로 계산·캐싱 | 기저 상태부터 테이블을 순서대로 채워 올라간다 |
| 구현 | 재귀 + 캐시(배열/맵) | 반복문 + DP 테이블 |
| 장점 | 필요한 상태만 계산해 스파스한 경우 유리, 직관적 | 호출 스택 없음, 상수 오버헤드 작음 |
| 단점 | 깊은 재귀는 스택 오버플로우 위험, 재귀 오버헤드 | 모든 상태를 채울 때 불필요 계산 가능, 전개 순서 설계 필요 |
| 메모리 | 캐시 + 호출 스택 | 테이블, 롤링 배열로 축소 가능 |
f(n)=f(n-1)+f(n-2) 호출이 중복되어 시간 복잡도 O(2^n)까지 커진다.public class Fibonacci {
public static void main(String[] args) {
int input = 6;
System.out.println(fibo(input));
}
public static int fibo(int n) {
if (n <= 1) return n;
else return fibo(n-2) + fibo(n-1);
}
}
// 결과 : 8
숫자가 작을 때는 차이는 없지만 숫자가 커지면 속도가 심각하게 저하된다. 만약 위 코드에서 input이 40이어도 시간이 걸리는 게 눈에 보이고 만약 100이라면 정말 오래 걸릴 것이다.
import java.util.*;
class FibTopDown {
static long[] dp;
static long fib(int n) {
if (n <= 1) return n;
if (dp[n] != -1) return dp[n];
dp[n] = fib(n - 1) + fib(n - 2);
return dp[n];
}
public static void main(String[] args) {
int n = 50;
dp = new long[n + 1];
Arrays.fill(dp, -1);
System.out.println(fib(n));
}
}
class FibBottomUp {
static long fib(int n) {
if (n <= 1) return n;
long prev2 = 0, prev1 = 1; // f(0), f(1)
for (int i = 2; i <= n; i++) {
long cur = prev1 + prev2;
prev2 = prev1;
prev1 = cur;
}
return prev1;
}
public static void main(String[] args) {
int n = 50;
System.out.println(fib(n));
}
}
출처:
https://dense.tistory.com/entry/dp
https://pixx.tistory.com/163
참고:
https://www.youtube.com/watch?v=0bqfTzpWySY&t=160s
정수 X에 사용할 수 있는 연산은 다음과 같이 세 가지 이다.
정수 N이 주어졌을 때, 위와 같은 연산 세 개를 적절히 사용해서 1을 만들려고 한다. 연산을 사용하는 횟수의 최솟값을 출력하시오.
목표 값이 1이고, 그 목표값을 구하기 위한 조건들을 봤다.
사용할 수 있는 연산이 ÷2, ÷3 , -1 이기 때문에 연산들을 이용하여 조건문을 완성해주면 원하는 코드를 얻을 수 있다고 생각했다.
i % 2 == 0, i % 3 == 0 같은 if문으로 가능한 연산만 후보로 삼아야 한다고 판단했다.
그리고 연산을 사용하는 횟수의 최솟값을 출력하라는 것이다.
이 부분이 헷갈렸던 부분인데 예제를 보면 10을 큰 수부터 나눠보기 시작하면 10 -> 5 -> 4 -> 2 -> 1 이렇게 4번의 연산이 필요하다. 하지만 정답은 10 -> 9 -> 3 -> 1 로 3이 정답이다.
또한 내가 재출한 코드에서는 구현하진 않았는데 , 내가 참고한 자료에선 2와 3의 배수, 즉 6으로 나눠지는 경우의 수도 있다. 내가 이 부분은 따로 구현하지 않았는데 통과된 이유는 ÷6은 결국 두 번의 허용 연산으로 이루어 지기 때문에 DP 점화식이 이미 그 경우를 자연스럽게 포함하고 있다고 한다.
그럼 코드로 짠다면 다음과 같은 경우의 수로 나눌 수 있겠다.
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
dp[1] = 0;
dp 배열을 선언해주고 있고, 인덱스 1..n을 쓰므로 크기를 n+1로 할당해주었다.1은 이미 1이므로 연산 0번이라 초기값을 → dp[1] = 0 이렇게 초기화해두었다.for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + 1; // 기본 후보: 1을 빼는 연산
if (i % 2 == 0) dp[i] = Math.min(dp[i], dp[i / 2] + 1); // 2로 나눠떨어지면 후보 갱신
if (i % 3 == 0) dp[i] = Math.min(dp[i], dp[i / 3] + 1); // 3으로 나눠떨어지면 후보 갱신
}
dp[i]는 “마지막 연산이 무엇이었는지”에 따라 다음 후보 중 최솟값을 결정한다.i → i-1을 마지막에 했다면 dp[i-1] + 1i가 2의 배수라면 i → i/2 가능 → dp[i/2] + 1i가 3의 배수라면 i → i/3 가능 → dp[i/3] + 1dp[i-1] + 1로 초기 세팅한 뒤, 조건이 맞을 때만 min으로 갱신해주었다.import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
dp[1] = 0;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + 1;
if (i % 2 == 0) dp[i] = Math.min(dp[i], dp[i / 2] + 1);
if (i % 3 == 0) dp[i] = Math.min(dp[i], dp[i / 3] + 1);
}
System.out.println(dp[n]);
br.close();
}
}
참고 : https://st-lab.tistory.com/133
dp[i] = 2×i 보드를 채우는 방법의 수dp[i−1]dp[i−2]dp[i] = dp[i−1] + dp[i−2]dp[1] = 1 ← 세로 1×2 ( 한 가지 !! )dp[2] = 2 ← 세로 1×2 두 장, 혹은 가로 2×1 두 장 ( 두 가지 !! )BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
dp[1] = 1;
if (n >= 2) dp[2] = 2;
dp[i]는 “2×i를 채우는 방법 수” 이다.dp[1]=1, dp[2]=2를 기저로 두었다.n=1인 입력도 있으므로 dp[2] 대입은 조건부(n≥2)로 처리해주었다.for (int i = 3; i <= n; i++) {
dp[i] = (dp[i - 1] + dp[i - 2]) % 10007;
}
i−1, i−2)를 더해주었다 !!import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
dp[1] = 1;
if (n >= 2) dp[2] = 2;
for (int i = 3; i <= n; i++){
dp[i] = (dp[i - 1] + dp[i - 2]) % 10007;
}
System.out.println(dp[n]);
br.close();
}
}
참고: https://www.youtube.com/watch?v=HmvD3X5pme8&t=437s
dp[i] = dp[i-1] + dp[i-2]를 만들었었다.i-2 부분이 두 배가 된다. 즉,dp[i] = dp[i-1] + 2*dp[i-2]dp[i] = 2×i 보드를 채우는 방법의 수dp[i−1]dp[i−2]dp[i−2]dp[i−2] + dp[i−2] = 2*dp[i−2]dp[i] = dp[i−1] + 2*dp[i−2]dp[1] = 1dp[2] = 3 (세로×2, 가로×2, 2×2 한 장 → 총 3가지)BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
dp[1] = 1;
if (n >= 2) dp[2] = 3;
dp[2] 값이 2 x 2 때문에 3으로 바뀐다for (int i = 3; i <= n; i++){
dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 2]) % 10007; // = dp[i-1] + 2*dp[i-2]
}
dp[i - 2] 에서 마지막 두 열을 덮는 방법이 (가로×2, 2×2)로 두 가지이기 때문에 2번 더해준다 !import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
dp[1] = 1;
if(n >= 2) dp[2] = 3;
for(int i = 3; i <= n; i++){
dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 2]) % 10007;
}
System.out.println(dp[n]);
br.close();
}
}
P(n)을 구해 출력한다. (입력: T개, 각 n은 1 ≤ n ≤ 100)1, 1, 1, 2, 2, 3, 4, 5, 7, 9, ... 처럼 되어있따 ! 이 배열을 보면 한가지 규칙이 있다는 걸 눈치챌 수 있다. n이 6 이상일 때 부터 n-2 번째와 n-3 번째의 수를 합 한 것이 자신의 숫자가 되는 걸 알아차릴 수 있었고 , 그걸 식으로 나타내면P(n) = P(n-2) + P(n-3) (n ≥ 6)P(1)=P(2)=P(3)=1
P(4)=P(5)=2dp[1..100]을 한 번 채워두고, 각 n에 대해 dp[n] 출력import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int T = Integer.parseInt(br.readLine());
long[] dp = new long[101];
dp[1] = dp[2] = dp[3] = 1;
dp[4] = dp[5] = 2;
for (int i = 6; i <= 100; i++) {
dp[i] = dp[i - 2] + dp[i - 3];
}
for (int t = 0; t < T; t++) {
int n = Integer.parseInt(br.readLine());
System.out.println(dp[n]);
}
}
}
참고: https://st-lab.tistory.com/127
… + dp[i-1]… + dp[i-2]dp[i] = 앞에서부터 i자리까지 해석하는 경우의 수 (편하게 쓰려고 빈 문자열도 한 경우로 보고 dp[0] = 1로 둔다.)1~9)면: dp[i] += dp[i-1]10~26)면: dp[i] += dp[i-2]dp[i] %= 1_000_000BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
int n = s.length();
int[] dp = new int[n + 1];
dp[0] = 1;
if (s.charAt(0) == '0') {
System.out.println(0);
return;
} else {
dp[1] = 1;
}
dp[0]=1: 빈 문자열은 1가지로 취급(점화식 편의를 위한 표준 트릭).1~9이므로 dp[1]=1.for (int i = 2; i <= n; i++) {
int one = s.charAt(i - 1) - '0'; // 한 자리
int two = Integer.parseInt(s.substring(i - 2, i)); // 두 자리
if (one >= 1 && one <= 9) {
dp[i] = (dp[i] + dp[i - 1]) % 1000000; // 현재 자리만 해석
}
if (two >= 10 && two <= 26) {
dp[i] = (dp[i] + dp[i - 2]) % 1000000; // 앞자리와 묶어 해석
}
}
System.out.println(dp[n]);
one이 1~9면 한 자리 해석 가능 → dp[i-1] 더함.two가 10~26이면 두 자리 해석 가능 → dp[i-2] 더함.one==0이면 첫 조건이 자동으로 스킵된다. 대신 two==10 또는 two==20일 때만 두 자리로 처리되어 살아남는다.import java.io.*;
public class Main {
~~~~ public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
int n = s.length();
int[] dp = new int[n + 1];
dp[0] = 1;
if (s.charAt(0) == '0') {
System.out.println(0);
return;
} else {
dp[1] = 1;
}
for (int i = 2; i <= n; i++) {
int one = s.charAt(i - 1) - '0';
int two = Integer.parseInt(s.substring(i - 2, i));
if (one >= 1 && one <= 9) {
dp[i] = (dp[i] + dp[i - 1]) % 1000000;
}
if (two >= 10 && two <= 26) {
dp[i] = (dp[i] + dp[i - 2]) % 1000000;
}
}
System.out.println(dp[n]);
}
}
참고: https://tussle.tistory.com/1137
dp[i] = 3×i 보드를 채우는 방법의 수 로 가정했다.0.dp[0] = 1 : 아무것도 안 놓는 1가지dp[2] = 3 : 3×2를 채우는 3가지 기본 패턴3 * dp[i-2]2 * (dp[i-4] + dp[i-6] + ... + dp[0])dp[i] = 3*dp[i-2] + 2*(dp[i-4] + dp[i-6] + ... + dp[0]) (i는 짝수, i≥4)N=2 → 3가지 → DP[2]=3N=4 → 3*DP[2]=9에 특수 무늬 2개 추가 → DP[4]=11N=6 → 3*DP[4] + 2*DP[2] + 2*DP[0] = 3*11 + 2*3 + 2*1 = 41int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
if (n % 2 == 1) {
System.out.println(0); // 홀수는 불가능
return;
}
dp[0] = 1;
dp[2] = 3;
dp[0]=1은 뒤에 나올 짝수들을 매핑하기 위해서 처음에 초기화 해주었다!for (int i = 4; i <= n; i += 2) {
dp[i] = dp[i - 2] * 3; // 기본 3가지 패턴
for (int j = i - 4; j >= 0; j -= 2) {
dp[i] += dp[j] * 2; // 누적 특수 무늬(각 2가지)
}
}
System.out.println(dp[n]);
dp[i-4], dp[i-6], …, dp[0]를 모두 더해 특수 무늬를 반영한다.import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
int[] dp = new int[n + 1];
if (n % 2 == 1) {
System.out.println(0); // 홀수 n은 채울 수 없음
return;
}
// 타일이 없을 경우의 수는 아무것도 채우지 않는 경우이다
dp[0] = 1;
// 3x1 타일을 채울 수 있는 경우의 수는 0개이다.
dp[1] = 0;
// 3x2 타일을 채울 수 있는 경우의 수는 3개이다.
dp[2] = 3;
// N은 홀수가 될 수 없고 짝수만 될 수 있기 때문에 2씩 증가를 한다.
for (int i = 4; i <= n; i += 2) {
dp[i] = dp[i - 2] * dp[2] + 2;
for (int j = i - 2; j >= 4; j -= 2) {
dp[i] += dp[i-j] * 2;
}
}
System.out.println(dp[n]);
}
}
참고: https://velog.io/@newtownboy/JAVA2133%EB%B2%88-%ED%83%80%EC%9D%BC-%EC%B1%84%EC%9A%B0%EA%B8%B0
먼저 작은 값을 일일히 대입해보면서 감을 잡는 방법으로 진행했다.
(1)(1+0, 0+1)(0+1+0, 1+0+0, 0+0+1)(2)(2+0, 0+2, 1+1)(2+0+0, 0+2+0, 0+0+2, 0+1+1, 1+0+1, 1+1+0)(3)(3+0, 0+3, 2+1, 1+2)(3+0+0, 0+3+0, 0+0+3, 1+1+1, 2+0+1, 1+0+2, 0+1+2, 0+2+1, 1+2+0, 2+1+0)이 패턴에서 바로 점화식이 나온다:
dp[n][k] = dp[n-1][k] + dp[n][k-1]
n-1, k)와n, k-1)의 합.dp[n][k] = 합이 n이 되도록 k개 수로 만드는 방법 수dp[n][1] = 1 (하나로 n을 만드는 방법은 n 하나뿐)dp[1][k] = k (1을 k개로 만드는 방법은 k가지)dp[n][0] = 0 (항이 0개면 양의 합은 만들 수 없음)dp[n][k] = (dp[n-1][k] + dp[n][k-1]) % MODint[][] dp = new int[N + 1][K + 1];
for (int i = 0; i <= N; i++) {
dp[i][0] = 0;
dp[i][1] = 1;
}
for (int i = 0; i <= K; i++) {
dp[1][i] = i;
}
dp[i][1]=1, dp[1][i]=i 두 줄로 N=1, K=1 축의 기저를 고정.dp[i][0]=0으로 항 0개는 불가능하게 처리.for (int i = 2; i <= N; i++) {
for (int j = 2; j <= K; j++) {
dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % MOD;
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static int N;
static int K;
static int MOD = 1_000_000_000;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
K = Integer.parseInt(st.nextToken());
int result = countWaysToSum();
System.out.println(result);
}
public static int countWaysToSum() {
int[][] dp = new int[N + 1][K + 1];
for (int i = 0; i <= N; i++) {
dp[i][0] = 0;
dp[i][1] = 1;
}
for (int i = 0; i <= K; i++) {
dp[1][i] = i;
}
for (int i = 2; i <= N; i++) {
for (int j = 2; j <= K; j++) {
dp[i][j] = (dp[i - 1][j] + dp[i][j - 1]) % MOD;
}
}
return dp[N][K];
}
}
참고: https://superohinsung.tistory.com/293
i열에서 윗줄을 고르면 직전(i-1)은 아랫줄만 가능, i-2는 양쪽 줄 모두에서 가능.dp[0][i] = i열의 윗 스티커를 선택했을 때 얻을 수 있는 최대 점수 dp[1][i] = i열의 아랫 스티커를 선택했을 때 얻을 수 있는 최대 점수dp[0][0] = sticker[0][0]
dp[1][0] = sticker[1][0]
n > 1 일 때
dp[0][1] = dp[1][0] + sticker[0][1]
dp[1][1] = dp[0][0] + sticker[1][1]i ≥ 2)dp[0][i] = max(dp[1][i-1], dp[1][i-2]) + sticker[0][i]
dp[1][i] = max(dp[0][i-1], dp[0][i-2]) + sticker[1][i]
i열 윗줄을 고르면, i-1에서는 아랫줄만 가능, i-2에서는 아랫줄 선택으로 끝난 상태와 이어 붙이는 게 안전하므로 dp[1][i-2]까지 비교.answer = max(dp[0][n-1], dp[1][n-1])
int t = Integer.parseInt(br.readLine());
while (t-- > 0) {
int n = Integer.parseInt(br.readLine());
int[][] sticker = new int[2][n];
int[][] dp = new int[2][n];
// 입력
...
// 초기값
dp[0][0] = sticker[0][0];
dp[1][0] = sticker[1][0];
if (n > 1) {
dp[0][1] = dp[1][0] + sticker[0][1];
dp[1][1] = dp[0][0] + sticker[1][1];
}
n=1일 때는 초기값만으로 처리되고 바로 답을 낸다.for (int i = 2; i < n; i++) {
dp[0][i] = Math.max(dp[1][i - 1], dp[1][i - 2]) + sticker[0][i];
dp[1][i] = Math.max(dp[0][i - 1], dp[0][i - 2]) + sticker[1][i];
}
int maxScore = Math.max(dp[0][n - 1], dp[1][n - 1]);
import java.io.*;
import java.util.StringTokenizer;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int t = Integer.parseInt(br.readLine());
StringBuilder sb = new StringBuilder();
while (t-- > 0) {
int n = Integer.parseInt(br.readLine());
int[][] sticker = new int[2][n];
int[][] dp = new int[2][n];
// 입력
for (int i = 0; i < 2; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 0; j < n; j++) {
sticker[i][j] = Integer.parseInt(st.nextToken());
}
}
dp[0][0] = sticker[0][0];
dp[1][0] = sticker[1][0];
if (n > 1) {
dp[0][1] = dp[1][0] + sticker[0][1];
dp[1][1] = dp[0][0] + sticker[1][1];
}
// DP 진행
for (int i = 2; i < n; i++) {
dp[0][i] = Math.max(dp[1][i - 1], dp[1][i - 2]) + sticker[0][i];
dp[1][i] = Math.max(dp[0][i - 1], dp[0][i - 2]) + sticker[1][i];
}
int maxScore = Math.max(dp[0][n - 1], dp[1][n - 1]);
sb.append(maxScore).append("\n");
}
System.out.print(sb);
}
}
참고: https://velog.io/@yanghl98/%EB%B0%B1%EC%A4%80-9465-%EC%8A%A4%ED%8B%B0%EC%BB%A4-JAVA
인접한 모든 자리의 차이가 1인 수를 계단 수라고 한다.
N이 주어질 때, 길이가 N인 계단 수가 총 몇 개 있는지 구해보자. 0으로 시작하는 수는 계단수가 아니다.
첫째 줄에 N이 주어진다. N은 1보다 크거나 같고, 100보다 작거나 같은 자연수이다.
첫째 줄에 정답을 1,000,000,000으로 나눈 나머지를 출력한다.
d의 이웃은 d-1 또는 d+1.d=0이면 이웃은 1만, d=9이면 8만 가능.len)를 1씩 늘리며 마지막 자릿수 기준 DP로 전이하는 구조가 자연스럽다.dp[len][d] = 길이 len에서 *마지막 자릿수가 d인 계단 수의 개수.d는 이전 길이의 d-1 또는 d+1에서만 올 수 있음(경계 0↔1, 9↔8).MOD=1,000,000,000으로 나머지 처리.dp[1][1..9] = 1, dp[1][0] = 0 (선행 0 금지)d = 0 → dp[len][0] = dp[len-1][1]d = 9 → dp[len][9] = dp[len-1][8]1 ≤ d ≤ 8 → dp[len][d] = dp[len-1][d-1] + dp[len-1][d+1]% MOD 처리import java.io.*;
import java.util.*;
public class Main {
static final long MOD = 1_000_000_000L;
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int N = Integer.parseInt(br.readLine());
long[] prev = new long[10];
for (int d = 1; d <= 9; d++) prev[d] = 1; // dp[1][1..9] = 1
for (int len = 2; len <= N; len++) {
long[] cur = new long[10];
cur[0] = prev[1] % MOD;
for (int d = 1; d <= 8; d++) {
cur[d] = (prev[d - 1] + prev[d + 1]) % MOD;
}
cur[9] = prev[8] % MOD;
prev = cur;
}
long ans = 0;
for (int d = 0; d <= 9; d++) ans = (ans + prev[d]) % MOD;
if (N == 1) ans = 9; // dp[1] 합은 1..9의 9개
System.out.println(ans % MOD);
}
}