728x90
728x90

문제 링크 : https://www.acmicpc.net/problem/10164

 

10164번: 격자상의 경로

입력의 첫째 줄에는 격자의 행의 수와 열의 수를 나타내는 두 정수 N과 M(1 ≤ N, M ≤ 15), 그리고 ○로 표시된 칸의 번호를 나타내는 정수 K(K=0 또는 1 < K < N×M)가 차례로 주어지며, 각 값은 공백으

www.acmicpc.net

문제

행의 수가 N이고 열의 수가 M인 격자의 각 칸에 1부터 N×M까지의 번호가 첫 행부터 시작하여 차례로 부여되어 있다. 격자의 어떤 칸은 ○ 표시가 되어 있다. (단, 1번 칸과 N × M번 칸은 ○ 표시가 되어 있지 않다. 또한, ○ 표시가 되어 있는 칸은 최대 한 개이다. 즉, ○ 표시가 된 칸이 없을 수도 있다.) 

행의 수가 3이고 열의 수가 5인 격자에서 각 칸에 번호가 1부터 차례대로 부여된 예가 아래에 있다. 이 격자에서는 8번 칸에 ○ 표시가 되어 있다.

 

격자의 1번 칸에서 출발한 어떤 로봇이 아래의 두 조건을 만족하면서 N×M번 칸으로 가고자 한다. 

  • 조건 1: 로봇은 한 번에 오른쪽에 인접한 칸 또는 아래에 인접한 칸으로만 이동할 수 있다. (즉, 대각선 방향으로는 이동할 수 없다.)
  • 조건 2: 격자에 ○로 표시된 칸이 있는 경우엔 로봇은 그 칸을 반드시 지나가야 한다. 

위에서 보인 것과 같은 격자가 주어질 때, 로봇이 이동할 수 있는 서로 다른 경로의 두 가지 예가 아래에 있다.

  • 1 → 2 → 3 → 8 → 9 → 10 → 15
  • 1 → 2 → 3 → 8 → 13 → 14 → 15

격자에 관한 정보가 주어질 때 로봇이 앞에서 설명한 두 조건을 만족하면서 이동할 수 있는 서로 다른 경로가 총 몇 개나 되는지 찾는 프로그램을 작성하라. 

입력

입력의 첫째 줄에는 격자의 행의 수와 열의 수를 나타내는 두 정수 N과 M(1 ≤ N, M ≤ 15), 그리고 ○로 표시된 칸의 번호를 나타내는 정수 K(K=0 또는 1 < K < N×M)가 차례로 주어지며, 각 값은 공백으로 구분된다. K의 값이 0인 경우도 있는데, 이는 ○로 표시된 칸이 없음을 의미한다. N과 M이 동시에 1인 경우는 없다.

출력

주어진 격자의 정보를 이용하여 설명한 조건을 만족하는 서로 다른 경로의 수를 계산하여 출력해야 한다. 

서브태스크

번호배점제한
1 9 1 ≤ N, M ≤ 5, K = 0
2 24 1 ≤ N, M ≤ 5, 1 < K < N × M
3 23 1 ≤ N, M ≤ 15, K = 0
4 44 원래의 제약조건 이외에 아무 제약조건이 없다.

 


접근 방법

처음에는 조합으로 풀었다가 32점이 나와서 dp로 문제를 해결했다.

 

 

처음 접근 방법은 K가 0이 아니면

K의 좌표를 구한 다음 -> 그 좌표까지 가려면 우측 방향으로 몇번 가야하는지와 아래 방향으로 몇 번 가야하는지를 조합을 사용해서 계산햇다. 

예제를 예시로 들면 아래에서 K까지 가려면 처음 [0][0]에서 우측으로 두 번, 아래 방향으로 한 번 가면 되는 것이다.

총 세번 이동을 해야하는데 그 중에 아래 화살표들을 순서 상관 없이 나열하는 조합의 경우의 수를 구하면 된다. 

➡️➡️⬇️

➡️⬇️➡️

⬇️➡️➡️

 

import java.util.Scanner;

public class Main {

    static int combi(int n, int r){
        if(n==r || r==0) return 1;
        return combi(n-1, r-1) + combi(n-1,r);
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();
        int M = sc.nextInt();
        int K = sc.nextInt();
        int ans = 0;

        if(K!=0){
            int kRow = K/M;
            int kCol = K%M -1;

            int first = combi(kRow+kCol, kCol);
            int second = combi(M-kCol-1 + N-kRow-1, N-kRow-1);
            ans = first*second;
        }
        else{
            ans = combi(N+M-2, N-1);
        }
        System.out.println(ans);

    }
}

 

 

그러나 이 방법으로는 32점밖에 안 나와서 N,M이 커지면 서브테스크 3번부터 막히는 것 같아 dp로 다시 풀었다.

dp로 풀때 주의할 점은 K의 위치를 잘 구해야 한다는 것이다.

예제로 주어진 문제로만 푸는 것이 아니라

반레로 K가 M으로 딱 나눠서 떨어지는 경우 즉 입력 값이 (2, 2, 2) 인 케이스도 생각하면 서브 테스크 3,4번도 모두 맞출 수 있다

import java.util.Scanner;

public class BJOJ_10164_격자상의경로 {

    static int combi(int n, int r){
        if(n==r || r==0) return 1;
        return combi(n-1, r-1) + combi(n-1,r);
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();
        int M = sc.nextInt();
        int K = sc.nextInt();
        int ans = 0;
        int[][] dp = new int[N][M];
        dp[0][0] = 0;

        if(K!=0){
            int kRow = K/M;
            int kCol = K%M;
            if(K%M == 0) {		//K가 오른쪽 끝에 붙어있는 경우 고려
                kRow -= 1;
                kCol = M-1;
            }else kCol-=1;


            for(int i = 1; i<=kRow; i++) {
                dp[i][0] = 1;
            }
            for(int i = 1; i<=kCol; i++){
                dp[0][i] = 1;
            }
            for(int i = 1; i<=kRow; i++){
                for(int j = 1; j<=kCol; j++){
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
            for(int i = kRow+1; i<N; i++) dp[i][kCol] = dp[kRow][kCol];
            for(int j = kCol+1; j<M; j++) dp[kRow][j] = dp[kRow][kCol];
            for(int i = kRow+1; i<N; i++){
                for(int j = kCol+1; j<M; j++){
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }

        }
        else{
            for(int i = 1; i<N; i++) {
                dp[i][0] = 1;
            }
            for(int i = 1; i<M; i++){
                dp[0][i] = 1;
            }
            for(int i = 1; i<N; i++){
                for(int j = 1; j<M; j++){
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
        }
        System.out.println(dp[N-1][M-1]);


    }
}

 

 

728x90
728x90

문제 링크 : https://www.acmicpc.net/problem/2240

 

2240번: 자두나무

자두는 자두를 좋아한다. 그래서 집에 자두나무를 심어두고, 여기서 열리는 자두를 먹고는 한다. 하지만 자두는 키가 작아서 자두를 따먹지는 못하고, 자두가 떨어질 때까지 기다린 다음에 떨어

www.acmicpc.net

 

문제

자두는 자두를 좋아한다. 그래서 집에 자두나무를 심어두고, 여기서 열리는 자두를 먹고는 한다. 하지만 자두는 키가 작아서 자두를 따먹지는 못하고, 자두가 떨어질 때까지 기다린 다음에 떨어지는 자두를 받아서 먹고는 한다. 자두를 잡을 때에는 자두가 허공에 있을 때 잡아야 하는데, 이는 자두가 말랑말랑하여 바닥에 떨어지면 못 먹을 정도로 뭉개지기 때문이다.

매 초마다, 두 개의 나무 중 하나의 나무에서 열매가 떨어지게 된다. 만약 열매가 떨어지는 순간, 자두가 그 나무의 아래에 서 있으면 자두는 그 열매를 받아먹을 수 있다. 두 개의 나무는 그다지 멀리 떨어져 있지 않기 때문에, 자두는 하나의 나무 아래에 서 있다가 다른 나무 아래로 빠르게(1초보다 훨씬 짧은 시간에) 움직일 수 있다. 하지만 자두는 체력이 그다지 좋지 못해서 많이 움직일 수는 없다.

자두는 T(1≤T≤1,000)초 동안 떨어지게 된다. 자두는 최대 W(1≤W≤30)번만 움직이고 싶어 한다. 매 초마다 어느 나무에서 자두가 떨어질지에 대한 정보가 주어졌을 때, 자두가 받을 수 있는 자두의 개수를 구해내는 프로그램을 작성하시오. 자두는 1번 자두나무 아래에 위치해 있다고 한다.

입력

첫째 줄에 두 정수 T, W가 주어진다. 다음 T개의 줄에는 각 순간에 자두가 떨어지는 나무의 번호가 1 또는 2로 주어진다.

출력

첫째 줄에 자두가 받을 수 있는 자두의 최대 개수를 출력한다.

예제 입력 1 

7 2
2
1
1
2
2
1
1

예제 출력 1 

6

 

 


접근 방법

처음에는 자두의 위치를 1) 그대로 있는 경우와 2) 옆 나무로 움직이는 경우 두 가지 케이스로 분류하려고 했으나, 자두의 위치 외에도 고려해야할 변수가 두개가 더 있었다. ( 현재 몇초인지(t)와 t초에 몇번째 자두나무에서 자두가 떨어지는지) 

즉 총 세개의 변수를 고려해야함을 알았는데

추가로 매 t초마다 하는 선택이 최선의 선택이 아니며, 그렇다고 모든 경우의 수를 다 조사해 볼 수는 없기 때문에 dp로 풀기로 했다. 

 

dp 배열 및 각 인덱스 의미는 다음과 같다. 

dp[t][w][1] : 현재 t초이며(t초 지남), t초까지 w만큼 이동했고, 그때의 자두의 현재 위치는 1일 때 -> 먹을 수 있는 자두의 최대 갯수

dp[t][w][2] : 현재 t초이며(t초 지남), t초까지 w만큼 이동했고, 그때의 자두의 현재 위치는 2일 때 -> 먹을 수 있는 자두의 최대 갯수

 

이제 및 [몇번째 자두나무에서 자두가 떨어지는지] 및 [자두의 이동 여부] 케이스를 분류해서 생각해보자.

각각 2가지 경우의 수가 있다. 

-> 1번째 자두나무에서 자두가 떨어지거나 or 2번째 자두나무에서 자두가 떨어지거나

-> 그대로 있거나 Or 옆 나무로 이동하거나

 

 

Case1. 1번째 자두나무에서 자두가 떨어질 때

경우 1) 현재 자두가 1번에 있을 때 : 자두 먹을 수 있음

//자두가 t초째에 1번 자두나무 아래에 있을 때 (자두 먹은 케이스)
dp[t][w][1] = Math.max(dp[t-1][w][1], dp[t-1][w-1][2])+ 1;

=> 해석 : 1초 전에, 안 움직이고(w그대로), 1번에 있을 때 (안 움직여서 1번 그대로)

          vs 1초 전에, 움직이고 (w-1), 2번에 있을 때(2번에 있었으니까 1번으로 옮긴것)

중에 최대값을 구하고, 거기에 1을 더한다. 1을 더한 이유는

1번째 자두나무에서 자두가 떨어지는 Case1. 일 때

현재 자두의 위치가 1번 나무 아래에 있는 경우1) 이기 때문이다. 

 

경우 2) 현재 자두가 2번에 있을 때 : 자두 못 먹음 

//자두가 t초째에 2번 자두나무 아래에 있을 때
dp[t][w][2] = Math.max(dp[t-1][w-1][1], dp[t-1][w][2]);

 

=> 해석 : 1초 전에, 움직이고 (w-1), 1번에 있을 때 (1번에 있었으니까 2번으로 옮긴 것)) 

          vs 1초 전에, 안 움직이고(w그대로), 2번에 있을 때(안 움직여서 2번 그대로)

중에 최대값을 구하고, 이번엔 1을 안 더한다. 안 더한 이유는

1번째 자두나무에서 자두가 떨어지는 Case1. 일 때

현재 자두의 위치가 2번 나무 아래에 있는 경우2) 이기 때문이다. 

 

이런 방식으로 매 시간에 따라(t가 1부터 T일때까지)

w가 1일때부터 W일때까지 dp를 채워나간다. 

 

그리고 마지막에 모든 w에 대해서 dp[T][w][1], dp[T][w][2] 값 들 중에 최대값을 구한다. 

 

import java.util.*;

public class BOJ_2240_자두나무 {
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        int W = sc.nextInt();
        int treeArr[] = new int[1001];
        int dp[][][] = new int[T+1][W+1][3];
        treeArr[0] = 1; //맨 처음 1번 나무에서 시작
        for(int i = 1; i<=T; i++){
            treeArr[i] = sc.nextInt();
        }

        //초기 값 셋팅
        if(treeArr[1] == 1){    //1초일 때, 첫번째 자두가 1번 나무에서 떨어질 경우(초기에 자두는 1번 나무 아래 있음)
            dp[1][0][1] = 1;    //1초일 때, 0번 움직여서, 현재 위치 그대로(1)   -> 자두 1개 먹음 (안움직이고 먹음)
            dp[1][1][2] = 0;    //1초일 때, 1번 움직여서, 현재 위치 바뀌고(2로) => 자두 0개 먹음 (움직이고 못먹음)
        }
        else{                   //1초일 때, 첫번째 자두가 2번 나무에서 떨어질 경우
            dp[1][0][1] = 0;    //1초일 때, 0번 움직이고, 현재 위치 그대로(1) -> 자두 0개 먹음 (안움직이고 못먹음)
            dp[1][1][2] = 1;    //1초일 때, 1번 움직이고, 현재 위치 바뀌고(2로) -> 자두 1개 먹음 (움직이고 먹음)

        }


        for(int t = 2; t<=T; t++){

            if(treeArr[t] == 1){    //1번 자두나무에서 자두가 떨어질 때
                //w = 0일 때 (안 움직였을 때) 현재 위치에 따른 초기 값 셋팅
                dp[t][0][1] = dp[t-1][0][1] + 1;    //현재 위치 1이면 = 0번 움직이고, 현재 위치 그대로 (안 움직이고 먹음)
                dp[t][0][2] = dp[t-1][0][2];        //현재 위치 2이면 = 0번 움직이고, 현재 위치 그대로 (안 움직이고 못 먹음)

                for(int w = 1; w<=W; w++){
                    //자두가 t초째에 1번 자두나무 아래에 있을 때 (자두 먹은 케이스)
                    dp[t][w][1] = Math.max(dp[t-1][w][1], dp[t-1][w-1][2])+ 1;
                    //자두가 t초째에 2번 자두나무 아래에 있을 때
                    dp[t][w][2] = Math.max(dp[t-1][w-1][1], dp[t-1][w][2]);
                }
            }

            else{                   //2번 자두나무에서 자두가 떨어질 때
                //w = 0일 때 (안 움직였을 때) 현재 위치에 따른 초기 값 셋팅
                dp[t][0][1] = dp[t-1][0][1];        //현재 위치 1이면 = 0번 움직이고, 현재 위치 그대로 (안 움직이고  못 먹음)
                dp[t][0][2] = dp[t-1][0][2] + 1;    //현재 위치 2이면 = 0번 움직이고, 현재 위치 그대로 (안 움직이고 먹음)



                for(int w = 1; w<=W; w++){
                    //자두가 t초째에 1번 자두나무 아래에 있을 때
                    dp[t][w][1] = Math.max(dp[t-1][w][1], dp[t-1][w-1][2]);
                    //자두가 t초째에 2번 자두나무 아래에 있을 때 (자두 먹은 케이스)
                    dp[t][w][2] = Math.max(dp[t-1][w-1][1], dp[t-1][w][2]) + 1;
                }
            }


        }

        int ans = 0;    //최대 값을 구해야 함
        for(int w = 0; w<=W; w++){
            ans = Math.max(ans, Math.max(dp[T][w][1], dp[T][w][2]));
        }
        System.out.println(ans);
    }
}

 

 

 

728x90
728x90

 

문제 링크 : www.acmicpc.net/submit/11727/26381160

 

로그인

 

www.acmicpc.net

 

문제

2×n 직사각형을 1×2, 2×1과 2×2 타일로 채우는 방법의 수를 구하는 프로그램을 작성하시오.

아래 그림은 2×17 직사각형을 채운 한가지 예이다.

입력

첫째 줄에 n이 주어진다. (1 ≤ n ≤ 1,000)

출력

첫째 줄에 2×n 크기의 직사각형을 채우는 방법의 수를 10,007로 나눈 나머지를 출력한다.

 


 

접근 방법

 

가로의 길이가 1부터 n이 될 때까지 타일을 붙여나가며, 이때 두가지 케이스로 나눠서 경우의 수를 카운트했다.

붙여야하는 타일의 가로 길이가 1인 것과 2인 것을 나눴다.

 

 

Case 1) 가로 길이가 1인 타일을 붙여야 하는 경우

이 경우는 타일의 길이가 n-1가 되도록 붙이는 경우의 수와 같다. 길이가 n인 타일을 만드려면 n-1 타일에 2x1 타일을 추가하기만 하면 되기 때문이다.

 

 

Case 2) 가로 길이가 2인 타일을 붙여야 하는 경우

이 경우엔 두가지 경우로 또 나눠서 생각해야한다. 

 

2-1) 1x2 타일을 붙이는 경우(세로로 두개)

이 경우는 타일의 길이가 n-2가 되도록 붙이는 경우의 수와 같다. 길이가 n인 타일을 만드려면 n-2 타일에 1x2 타일 두개를 세로로 추가하기만 하면 되기 때문이다.

 

2-2) 2x2 타일을 붙이는 경우 

이 경우도 2-1과 같다. 2x2 타일을 추가만 하면 된다.

 

 

Case 1, 2를 조합해서 생각하면, 길이 n인 타일을 붙이는 경우의 수는,

길이 n-1인 타일을 붙이는 경우의 수 + 2*(길이 n-2인 타일을 붙이는 경우의 수) 로 나타낼 수 있다.

즉, dp[n] = dp[n-1] + 2*dp[n-2]인 것이다.

 

 

#include <iostream>
#include <vector>

using namespace std;

int dp[1001] = {0};

int main(){
    
    int n;
    cin >> n;
    
    dp[0] = 0;
    dp[1] = 1;
    dp[2] = 3;
    
    for(int i = 3; i<=n; i++){
        dp[i] = (2*dp[i-2] + dp[i-1]) % 10007;
    }
    cout << dp[n];
    
    return 0;
}

 

 

이때 주의할 점은 for문 안에서 dp[n] = dp[n-1] + 2*dp[n-2] 로 계산을 쭉 하고

마지막에 출력할 때 dp[n]%10007 을 하면 안된다는 것이다. 위의 코드와 같이 for문 안에서 나머지 계산을 해줘야한다.

 

문제 요구조건 자체가 mod 연산을 한 결과를 출력하라고 했으니, 처음에 dp[]에 값을 저장할 때 mod연산의 결과를 저장해야한다.

 

728x90

+ Recent posts