코딩 문제

백준 2057 - 팩토리얼 분해

토리쟁이 2023. 1. 4. 20:37

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

 

2057번: 팩토리얼 분해

음 아닌 정수 N이 주어졌을 때, 이 수를 서로 다른 정수 M(M ≥ 1)개의 팩토리얼의 합으로 나타낼 수 있는지 알아내는 프로그램을 작성하시오. 예를 들어 2=0!+1!로 나타낼 수 있지만, 5는 이와 같은

www.acmicpc.net

굉장히 많은 시간을 썼는데 풀지 못했다.

문제 설명도 부족한데 예제 입력도 하나만 딸랑 줘놓고 참나

다른 사람의 코드를 보고 간신히 이해했다.

 

맨 처음엔 이 문제가 입력된 숫자를 중복을 허용하고 팩토리얼들의 합으로 나타낼 수 있는지 없는지를 구하는 문제로 이해를 했다. 그래서 나온 코드는

import math

def find_result(n):
    i = 0
    print("i:", i)
    print("n", n)
    while True:
        fac = math.factorial(i)
        print("fac:", fac)
        if fac > n:  # 맞는 팩토리얼 존재 x
            return -1
        elif fac == n:  # 맞는 팩토리얼 존재
            if n == fac:
                return fac
        else:  # 적절한 팩토리얼이 나왔지만, 합까지는 도달하지 못 했을 때 => 계속해서 팩토리얼들을 찾아봐야함 ==> 합이 n과 같아질 떄 까지 ==> 안같아지면 -1 함수 종료
            print("팩토리얼 합 찾기 시작")
            n = n - fac
            while True:
                fac = find_result(n)
                n = n-fac
                if n == 0: # 팩토리얼의 합들이 존재할 때
                    return 1
            i += 1


n = int(input())
result = find_result(n)
if result == 1:
    print("YES")
else:
    print("NO")

재귀함수에 제일 약한 나인데 기껏 머리 터지도록 재귀함수 썼는데 틀렸다. 쩝,,

근데 생각해보면 중복을 허용해버리면 모든 수는 팩토리얼의 합으로 표현이 가능하다.

왜냐하면, 0!이나 1!은 1인데 1을 계속 더하면 그 수가 되겠지.. 

 

그래서 아 문제를 잘 못 이해하고 뻘짓했구나~하고 문제를 다시 보았다.

2 = 0! + 1! 이길래 주어진 숫자보다 작은 팩토리얼의 모든 합으로 표현할 수 있나 없나..하는 문제인줄

그래서 작성한 코드가

import math

n = int(input())
fac_num = []
i = 0

while True:
    num = math.factorial(i)
    if num <= n:
        fac_num.append(num)
        i += 1
    else:
        break
if n!=0:
    for i in range(len(fac_num)-1, -1, -1):
        n -= fac_num[i]
        if n == 0:
            print('YES')
            break

    if n!=0:
        print('NO')
else:
    print('NO')

이건데, 제출해보니까 자꾸 80%에서 오답처리가 되는 것이었다....

 

다른 사람의 정답 코드를 보니까 3을 입력했을 때 YES가 나오는데

이 코드는 3일 때 NO가 나온다.

문제 이해를 잘 못해서 3일 때 NO가 나오는게 맞는줄..

 

이 문제는 ㄹㅇ 해당 숫자가 서로 팩토리얼들의 합으로 표현될 수 있는지, 없는지 확인하는 문제이고(그대로 받아들일 것).. 이 문제를 풀려면 다음과 같은 로직이 필요하다.

 

1. 해당 숫자와 가장 가까운 값을 갖는 팩토리얼부터 차례대로 빼볼 것 

2. 뺀 숫자의 팩토리얼 리스트를 다시 구하기 단, 이미 사용된 팩토리얼은 사용이 불가능하므로 pop할 것

3. 뺀 숫자에 1~2의 과정을 다시 반복

 

이 1~3 로직을 반영하여 작성한 코드는 다음과 같다.

import math

n = int(input())
fac_num = []
i = 0

while True:  # 맨 처음 숫자에 올 수 있는 팩토리얼 리스트 구하기 => 어차피 그 다음 숫자의 팩토리얼 리스트는 해당 리스트보다 작으므로
    num = math.factorial(i)
    if num <= n:
        fac_num.append(num)
    else:
        break
    i += 1
if n==0:  # 0이 입력될 경우, 로직을 돌려볼 필요도 없이 NO
    print('NO')
else:
    if n in fac_num:  # 해당 숫자가 팩토리얼 수 그 자체일 경우, 로직을 돌려볼 필요도 없이 YES
        print("YES")
    else:
        while n>=0:  # 해당 숫자가 0이상일 때만 로직 실행
            fac_num =list(filter(lambda x: x<=n, fac_num))  # 해당 숫자의 팩토리얼 리스트(해당 숫자보다 큰 팩토리얼 숫자는 올 수 없음)
            if n in fac_num:  # 해당 숫자가 팩토리얼 그 자체일 경우, YES
                print('YES')
                break
            if len(fac_num) !=0:  # 해당 숫자가 팩토리얼 리스트를 갖을 경우
                n -= fac_num.pop()  # 해당 숫자에 가장 가까운 팩토리얼 수를 뺄 것(로직1) and pop을 시켜줘야 해당 리스트에서 제거되므로 중복을 방지할 수 있음
                if n==0:  # 뺀 결과가 0일 경우 ==> 합으로 표현이 가능하다는 의미
                    print('YES')
                    break
            else:  # 해당 숫자가 리스트를 안갖을 경우
                print('NO')  # 팩토리얼들의 합으로 나타낼 수 없음
                break

위에 보면 조건문들이 꽤 있는데 이건 그냥 시간 단축을 위해서 썼는데 별반 차이 없을 것 같다. 

어차피 pop()해서 접근하므로 해당 숫자가 팩토리얼일 경우, 바로 pop이 되므로 n=0이 바로 된다.

 

ex) 27 => YES

1. 27의 팩토리얼 리스트 : [1, 1, 2, 6, 24]

2. 27-24 = 3

3. 3의 팩토리얼 리스트: [1, 1, 2]

4. 3 - 2 = 1

5. 1의 팩토리얼 리스트: [1, 1]

6. 1 - 1 = 0 ==> YES!

 

ex) 19 => NO

1. 19의 팩토리얼 리스트: [1, 1, 2, 6]

2. 19 - 6 = 13

3. 13의 팩토리얼 리스트: [1, 1, 2]

4. 13 - 2 = 11

5. 7의 팩토리얼 리스트: [1, 1]

6. 7 - 1 = 6

7. 6의 팩토리얼 리스트: [] ==> NO!

 

스읍 이거 로직 생각을 못 해내겠음 .. 오늘 하루를 날렸네ㅋ

이해는 되는데 다시 풀라고 하라믄 못 풀겠다리..주륵