정보 AP

코드업 2832 - [상태 정의를 통한 탐색] 계단 오르기 1-1

Halfand 2023. 3. 31. 00:23

 

문제는 진작 풀었으나 이제야 올리는 코드업 2832번 티스토리

 

지금 시작합니다

 

1시간 30분동안 열심히 적은 글이 날아갔습니다.

다시 시작합니다.

 

 

 

 


 

문제

코드업 2832번: [상태 정의를 통한 탐색] 계단 오르기 1-1

철수가 계단을 올라가려고 한다.

계단은 모두 n칸으로 구성되어 있다.

철수는 한 번에 1칸, 2칸을 오를 수 있다.

철수가 k개 이하의 칸을 사용하여 0번째 칸에서 출발하여 n번째 칸으로 올라가는 서로 다른 방법의 수를 구하는 프로그램을 작성하시오.

만약 n = 3, k = 3 이면

- 1 2 : 0번째, 1번째, 3번째 계단을 이용하여 목표에 도달
- 2 1 : 0번째, 2번째, 3번째 계단을 이용하여 목표에 도달

로 모두 2가지 경우가 있다.

 

입력

첫 번째 줄에 n과 k가 공백을 기준으로 입력된다.

[입력값의 정의역]
[1 ≤ n ≤ 40]
[1 ≤ k ≤ 20]

 

출력

계단을 올라가는 서로 다른 경로의 수를 출력한다.

 

입력 예시

3 3

 

출력 예시

2

 


 

처음 문제를 읽고 나서 든 생각은

 

(뭔가 나만의 새로운, new, 新, Nouveau, Новый, नया, baru, 암튼 그러한 풀이를 찾고 싶다.)

   O     

.        o

 

였습니다. 

그러다 처음 생각해 냈던 것은 이진법을 이용한 계산이었는데요.  

 


 

** 이진법이란? **

이진법(二進法, binary)은 두 개의 숫자(1과 0)만을 이용하는 수 체계이다. 관습적으로 0과 1의 기호를 쓰며 이들로 이루어진 수를 이진수라고 한다. -위키백과-

 

철수가 올라갈 수 있는 계단의 수는 2칸 또는 1칸이므로, 올라가는 계단의 수를 나열하면 212221211... 처럼 2와 1의 배열이 됩니다. 이때 각 자리에서 -1을 해 주면 101110100...이 되므로 이진법을 사용해서 계산을 해도 좋을 것이란 생각이 든 거죠. 

 

그러나 조금 코드를 생각해 보면 c언어에서 이진법으로 수를 바꾼 후에 이진법으로 연산을 하는 것이 번거롭다는 것을 알 수 있습니다. 구현이 불가능 한 것은 아닐 것 같으나 코드가 길어질 것 같아, 안타깝지만 이 아이디어는 일단 접어두게 되었습니다.

 

언젠가 시간이 나면 이 방법으로도 한 번 도전해 보려 합니다! (믿는다 나자신...)

 


그렇다면 저는 어떤 아이디어를 가지고 문제를 풀었을까요?

 

또 고민을 하다가, 그날 점심을 먹는 도중 아이디어가 생각났습니다. 

신나서 먹던 점심을 뒤로 하고 혹시나 잊어버릴까봐 후다닥 제 패드에다가 메모했습니다.

(밥먹다 갑자기 뛰쳐나가서 앞에 있던 애들이 뭐야 저거 하는 표정을 짓더라고요ㅋㅋㅋㅋㅋㅋㅋ)

 

밥 먹다 적어놓은 메모.

 

그렇게 제가 떠올려낸 또 다른 아이디어는 nCr, 즉 조합을 이용한 풀이였습니다.

 

 


** 조합이란? **

수학에서 조합(組合, 문화어: 무이, 영어: combination)은 유한 개의 원소에서 주어진 수만큼의 원소들을 고르는 방법이다. 조합의 수는 이항 계수로 주어진다. -위키백과-

 

 

설명이 조금 딱딱하죠? 쉽게 말하자면 조합이란, n개의 원소에서 r개만큼의 원소를 고르는 방법을 말합니다. 

 

그럼 앞에서 말했던 nCr이란 무엇일까요? 그건 조합의 수를 구하는 공식을 보면 알 수 있습니다.

n개의 원소에서 r개만큼의 원소를 고르는 방법을 나타내는 조합의 공식

 

여기서 nCr은 n개의 원소중에서 r개만큼의 원소를 고르는 조합의 개수를 말합니다. 

따라서 조합의 수를 구한다 == nCr을 구한다 라고 말 할 수 있는 거죠.

 


 

핵심 idea

n이 짝수일 때 (2칸 올라가는 것을 2, 1칸 올라가는 것을 1이라고 두었습니다.)

 

1. 가장 적은 계단을 밟고 올라가는 방법 : 22222....22

2. 그 다음으로 적은 계단을 밟고 올라가는 방법: 위의 숫자들에서 2를 하나 빼고 1을 2개 넣었을 때 nCr 값 (n은 1에서 2의 개수, r은 1의 개수)

 - 왜 nCr인가?

1이 1개일 때의 계단을 오르는 방법의 수는

22222...21, 22222...12, ....... , 21222...22, 12222...22로  총 2의 개수만큼일 것이기 때문.

 

 

홀수일 때는 마지막 2를 1로 바꾼 후에 똑같이 세면 됩니다. 

 

 

이제 본격적으로 제가 짠 코드를 설명하겠습니다.


MY CODE

#include <stdio.h>
int n, k;
int noc; 

long long int fac(int a){     	
    if(a==1||a==0) return 1;
    return a*fac(a-1);       	
}

int ncr(){
    int i=0, n2=n/2; 
    if (n%2){
    	i++; n2++;
    } 
    for(; n2<k&&n2>i; n2++, i+=2){
        noc+=fac(n2)/(fac(n2-i)*fac(i));  
    }
    return noc;
}

int main(){
    scanf("%d%d", &n, &k);
    printf("%d", ncr());
    return 0;
}

 

 

원래는 코드에 주석을 달려고 했는데 너무 길어지다 보니 글을 발행했을 때는 코드가 이상해 지더라고요.

 

이런 식으로 말이죠...

 

하지만 그냥 넘어갈 수는 없죠. 아래에서 차근차근 설명해 드리겠습니다.

 

 

(생략)

int noc;

 

 //noc: number of cases, 계단을 오르는 경우의 수

 

 

long long int fac(int a){      
    if(a==1||a==0) return 1;
    return a*fac(a-1);       
}

 

//factorial 계산 함수

//a가 1이 되면 더 재귀를 돌리지 않아도 되므로 지금까지 곱한 값을 반환하며 함수를 종료합니다.

//팩토리얼의 정의에 따르면 0!=1이라고 합니다.

 

 

int ncr(){
    int i=0, n2=n/2; 
    if (n%2){  
     i++; n2++;   

 

//i1칸 올라가는 횟수, n2 (최소한의 계단만 밟고 n번째 계단까지 올라갈 때 밟는 계단의 수 - 1)입니다.

(왜 -1을 할까요? 한 번 생각해 보세요!)

//n이 짝수이면 (예를 들어 n==6) 2칸+2칸+2칸 올라가서 총 n/2인 3칸만 밟으면 되지만 n이 홀수일 시에는 (예를 들어 n==5) 2칸+2칸+1칸 올라가야 하기 때문에 밟는 계단의 수(n/2)+1=3개입니다.  

 

 

for(; n2<k&&n2>=i; n2++, i+=2){  
    noc+=fac(n2)/(fac(n2-i)*fac(i));  
}
return noc;

 

//i+=2인 이유: 한번에 2칸 올라가던 것을 한 칸씩 올라가려면 밟는 계단의 수가 *2가 되기 때문입니다. 

//위에서 봤던 nCr공식factorial 함수를 이용해 구현한 것입니다. 

 

 

 


 

과정 설명 순서도

 

과정 설명 순서도

 


 

생겼던 문제점

 

제가 한번에 뿅 코드를 짜 내지는 않았겠죠? 많은 시행착오를 겪으며 만들어진 코드입니다...

for(; n2<=k&&n2>i; n2++, i+=2)

이것은 문제가 있던 코드의 일부분인데요. 정확한 풀이가 나오는 코드와 어느 부분이 다른지 눈치 채셨나요?

 

정답은 n2 <= k 부분입니다. 정확한 풀이가 나오는 코드에는 등호가 들어가 있지 않습니다. 

그렇다면 왜, n2 <= k 라는 조건에서는 오답이 나오는 걸까요? 

 

 

이건 제가 코드를 짤 때 메모한 것인데요. 

 

 

 위 사진을 보시면 

 

 

라고 적어놓은 부분이 있습니다. 

 

이것은 n=6, k=5일때의 조합이 가능한 경우를 직접 나열해 본 것입니다.

 

보시면 2가 3개인 경우 1개, 2가 2개고 1이 2개인 경우 6개, 2가 1개고 1이 4개인 경우(5개일 것이므로 생략했음)를 적어놓았는데요. 위에 적어놓은 바에 따르면 n=6일때, k=5일때의 답은 1+6+5로 12개가 나와야 합니다. 

 

 

그러나 정확한 코드n=6과 k=5를 넣어 보면 

 

n=6, k=5대입

 

답이 12가 아니라 7이 나오는 것을 볼 수 있습니다.

 

 

제가 여기서 저지른 실수는, 바로 0번째 계단을 생각하지 않고 코드를 짰던 것인데요.

 

바닥이 아니라 0번째 계단이었던...

 

222와 2211, 21111 모두 각 자리의 합이 6, 즉 n이 되기 때문에 21111의 경우도 가능하다고 생각했던 것이 문제였습니다. 

 

 

그러나 각 자리의 2와 1의 합이 n이 된다는 것k개 이하의 계단을 밟는 것이 아닌, n개의 계단을 오르기 위한 조건이었습니다. 

 

 

위 그림과 같이 21111은 0번째 계단(바닥)까지 포함하여 총 6계단을 밟고 올라가는 것이 되기 때문에, 5인 k의 값보다 더 커지게 되어 문제가 생기게 되었던 것입니다. 

 

결국 제가 처음에 잘못 생각했던 n20번째 계단을 빼고 밟고 올라간 계단의 개수였습니다.

즉, n2의 진짜 의미란 ( 계단을 최대한 적게 밟고 올라가는 수 - 1 )이 되는 것이죠. 

이제 왜 제가 위 주석에서 -1을 했는지 이해하실 수 있을 거에요!

 


여담

Q. 왜 fac 함수에서는 int가 아니라 long long int를 사용했는가?

 

누가 봐도 명백한 오버플로우의 모습

- A. 그냥 int 썼다가 오버플로우 당함...

 


마무리하며...

긴 글 읽어주셔서 감사합니다.

 

 

아래는 코드업 2832번의 링크입니다. 저의 코드만이 이 문제를 해결하는 유일한 정답은 아니기 때문에, 제 글을 보신 분들도 한 번 본인만의 코드를 짜서 문제를 해결해 보시기를 바랍니다. 파이팅!!

 

https://codeup.kr/problem.php?id=2832&rid=23179 

 

[상태 정의를 통한 탐색] 계단 오르기 1-1

OO이가 계단을 올라가려고 한다. 계단은 모두 n칸으로 구성되어 있다. OO이는 한 번에 1칸, 2칸을 오를 수 있다. OO이가 k개 이하의 칸을 사용하여 0번째 칸에서 출발하여 n번째 칸으로 올라가는 서

codeup.kr

 

 

저는 그럼 이만 다음 과제를 하러 가보겠습니다. (살려주세요)