코딩테스트

3234. 준환이의 양팔저울[python]

한상규 2020. 12. 20. 23:47

dfs를 이용해서 이진탐색을 구현했더라도 시간초과 때문에 특정 조건을 마주치면 return을 해줘야 한다.

 

✔ 고려해준 사항 

1. dfs를 이용한 이진탐색 visited 리스트를 이용해서 이미 올려놓은 추는 다시 올려 놓지 않게 만들었다.

2. '왼쪽에 올려놓은 추의 합 * 2'가 '전체 추의 합' 보다 크다면 그 이후 탐색은 할 필요없이 2^k * k!(단, k는 남은 추의 개수)을 결과값에 더해주면 된다.

3. 반대로 오른쪽에 올려놓은 추의 합*2 가 전체 추의 합보다 크다면 그 이후 탐색은 할 필요없다. 

4. 조건대로 왼쪽에 올려놓은 추의 합보다 오른 쪽에 올려놓은 추의 합이 커지면 그 이후 탐색은 할 필요없다. 

5. 2의 n제곱, n 팩토리얼 값을 미리 계산해서 사용한다.

 

1~4번까지 적용해도 20/21개만 성공하고 시간초과가 떴다. 5번을 적용해주시 시간초과가 뜨지 않았다.

생각보다 math.factorial() 함수의 cost가 큰가 보다.

 

👍 배운점

1. 0팩토리얼은 1이다. 수학적으로도 0팩토리얼은 1이라고 한다.

꼭 수학적으로 생각하지 않더라도 

이 문제에서 2^k * k!를 계산할 때 k가 0인 경우를 계산하는 상황이 발생하는데 k의 0팩토리얼을 0이라고 생각해서 계속 이상한 값이 나왔다. k가 0인 경우는 _depth가 마지막까지 도달했을 때 if((_lsum)*2 > isum): 조건을 만족하면서 발생한다. 이 때 res에 +1을 해줘야 하기 때문에 0팩토리얼을 1로 해줘야 한다. 

 

 

2. 주어지는 데이터 값이 적다면 미리 값을 계산 하는 것이 시간초과를 해결할 수 있는 열쇠이다.

이 문제에서는 2의 n제곱과 n팩토리얼을 미리 계산하는 것이 시간초과를 해결하는데 도움이 되었다.

 

내 코드

def dfs(_depth, _lsum, _rsum):
    global n
    global visited
    global res
    global ilist
    global isum
    global __2n
    global __nFac



    if(_lsum < _rsum):
        return

    if((_lsum)*2 > isum):
        res += __2n[n-_depth] * __nFac[n-_depth]
        return

    if((_rsum)*2 > isum):
        return


    if(_depth == n):
        res += 1
        return


    for i in range(n):
        if(visited[i] == 0):
            visited[i] = 1
            dfs(_depth+1, _lsum+ilist[i], _rsum)
            dfs(_depth+1, _lsum, _rsum+ilist[i])
            visited[i] = 0

T = int(input())
for test_case in range(1, T + 1):
    n = int(input())

    __2n = [1,2,4,8,16,32,64,128,256]
    __nFac = [1,1,2,6,24, 120, 720, 5040, 40320, 362880]

    ilist = list(map(int, input().split()))
    isum = sum(ilist)
    res = 0

    visited = [0]*n
    
    for i in range(n):
        visited[i] = 1
        dfs(1, ilist[i], 0)
        visited[i] = 0

    print('#'+str(test_case)+' '+ str(res))