Post

[BOJ] 연산자 끼워넣기 - 14888 (S1)

[BOJ] 연산자 끼워넣기 - 14888 (S1)
시간 제한메모리 제한
2 초512 MB

문제

N개의 수로 이루어진 수열 A₁, A₂, …, Aₙ이 주어진다. 또, 수와 수 사이에 끼워넣을 수 있는 N-1개의 연산자가 주어진다. 연산자는 덧셈(+), 뺄셈(-), 곱셈(×), 나눗셈(÷)으로만 이루어져 있다.

우리는 수와 수 사이에 연산자를 하나씩 넣어서, 수식을 하나 만들 수 있다. 이때, 주어진 수의 순서를 바꾸면 안 된다.

예를 들어, 6개의 수로 이루어진 수열이 1, 2, 3, 4, 5, 6이고, 주어진 연산자가 덧셈(+) 2개, 뺄셈(-) 1개, 곱셈(×) 1개, 나눗셈(÷) 1개인 경우에는 총 60가지의 식을 만들 수 있다.

식의 계산은 연산자 우선 순위를 무시하고 앞에서부터 진행해야 한다. 또, 나눗셈은 정수 나눗셈으로 몫만 취한다. 음수를 양수로 나눌 때는 C++14의 기준을 따른다. 즉, 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼 것과 같다.

N개의 수와 N-1개의 연산자가 주어졌을 때, 만들 수 있는 식의 결과가 최대인 것과 최소인 것을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)이 주어진다. 둘째 줄에는 A₁, A₂, …, Aₙ이 주어진다. (1 ≤ Aᵢ ≤ 100) 셋째 줄에는 덧셈, 뺄셈, 곱셈, 나눗셈의 개수가 순서대로 주어진다.

출력

첫째 줄에 만들 수 있는 식의 결과의 최댓값을, 둘째 줄에는 최솟값을 출력한다.

풀이

이 문제는 백트래킹을 이용한 브루트포스 문제이다. 가능한 모든 연산자 조합을 시도하여 최대값과 최소값을 찾는다.

접근 방법

핵심 아이디어

  1. DFS를 이용하여 모든 연산자 배치 조합을 탐색
  2. 각 단계에서 사용 가능한 연산자를 선택하고 계산
  3. 모든 수를 사용했을 때 결과를 비교하여 최대/최소 갱신

백트래킹 과정

  • 현재 결과값과 사용할 다음 수를 가지고 재귀
  • 각 연산자 종류별로 남은 개수가 있으면 시도
  • 연산 후 다음 단계로 진행
  • 백트래킹: 연산자 개수 복구

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
operate = ['+', '-', '*','/']

N = int(input())
nums = list(map(int, input().split()))
operator_counter = list(map(int, input().split()))
operator_cnt = sum(operator_counter)

max_com = -float('inf')
min_com = float('inf')


def compute(a, b, op_i):
    if op_i == 0:
        return a + b
    elif op_i == 1:
        return a - b
    elif op_i == 2:
        return a * b
    elif op_i == 3:
        return int(a / b)

def dfs(operator_counter, result, n=0):
    global max_com, min_com
    if n == operator_cnt:
        max_com = max(max_com, result)
        min_com = min(min_com, result)
        return
    for i, cnt in enumerate(operator_counter):
        if cnt == 0:
            continue
        operator_counter[i] -= 1
        dfs(operator_counter, compute(result, nums[n+1], i), n+1)
        operator_counter[i] += 1


dfs(operator_counter, nums[0])

print(max_com)
print(min_com)

코드 설명

compute 함수

1
2
3
4
5
6
7
8
9
def compute(a, b, op_i):
    if op_i == 0:
        return a + b
    elif op_i == 1:
        return a - b
    elif op_i == 2:
        return a * b
    elif op_i == 3:
        return int(a / b)
  • 연산자 인덱스에 따라 계산 수행
  • 나눗셈은 int(a / b)로 처리 (음수 나눗셈 처리)

dfs 함수

1
2
3
4
5
6
def dfs(operator_counter, result, n=0):
    global max_com, min_com
    if n == operator_cnt:
        max_com = max(max_com, result)
        min_com = min(min_com, result)
        return
  • n: 현재까지 사용한 연산자 개수
  • 모든 연산자를 사용했으면 결과 갱신

백트래킹

1
2
3
4
5
6
for i, cnt in enumerate(operator_counter):
    if cnt == 0:
        continue
    operator_counter[i] -= 1
    dfs(operator_counter, compute(result, nums[n+1], i), n+1)
    operator_counter[i] += 1
  • 각 연산자 종류에 대해 남은 개수가 있으면 시도
  • 연산자 사용 (-= 1)
  • 재귀 호출
  • 백트래킹: 연산자 복구 (+= 1)

시간 복잡도

최악의 경우 N-1개의 연산자를 배치하는 모든 경우의 수는 (N-1)!이다. 하지만 중복된 연산자가 있으므로:

\[\frac{(N-1)!}{\text{각 연산자 개수의 팩토리얼 곱}}\]

N ≤ 11이므로 최대 10! / (중복) ≈ 3,628,800 정도로 충분히 시간 내에 해결 가능하다.

주의 사항

  1. 연산 순서: 연산자 우선순위 무시, 앞에서부터 계산
  2. 음수 나눗셈: int(a / b)로 처리 (C++14 기준)
  3. 초기값: 최대값은 -inf, 최소값은 +inf로 초기화
  4. 백트래킹: 연산자 개수를 원상복구해야 다른 경로 탐색 가능

음수 나눗셈 처리

파이썬의 // 연산자는 내림 나눗셈이므로 음수에서 C++과 다르게 동작한다:

  • C++: -7 / 3 = -2 (0으로 향하는 버림)
  • Python //: -7 // 3 = -3 (음의 무한대로 향하는 내림)
  • Python int(): int(-7 / 3) = -2 (0으로 향하는 버림)

따라서 int(a / b)를 사용해야 정확하다.

This post is licensed under CC BY 4.0 by the author.