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

Sierra·2022년 2월 24일
0

[BOJ] GraphTheory

목록 보기
20/30
post-thumbnail

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

문제

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

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

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

1+2+3-4×5÷6
1÷2+3+4-5×6
1+2÷3×4-5+6
1÷2×3-4+5+6
식의 계산은 연산자 우선 순위를 무시하고 앞에서부터 진행해야 한다. 또, 나눗셈은 정수 나눗셈으로 몫만 취한다. 음수를 양수로 나눌 때는 C++14의 기준을 따른다. 즉, 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼 것과 같다. 이에 따라서, 위의 식 4개의 결과를 계산해보면 아래와 같다.

1+2+3-4×5÷6 = 1
1÷2+3+4-5×6 = 12
1+2÷3×4-5+6 = 5
1÷2×3-4+5+6 = 7
N개의 수와 N-1개의 연산자가 주어졌을 때, 만들 수 있는 식의 결과가 최대인 것과 최소인 것을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 곱셈(×)의 개수, 나눗셈(÷)의 개수이다.

출력

첫째 줄에 만들 수 있는 식의 결과의 최댓값을, 둘째 줄에는 최솟값을 출력한다. 연산자를 어떻게 끼워넣어도 항상 -10억보다 크거나 같고, 10억보다 작거나 같은 결과가 나오는 입력만 주어진다. 또한, 앞에서부터 계산했을 때, 중간에 계산되는 식의 결과도 항상 -10억보다 크거나 같고, 10억보다 작거나 같다.

예제 입출력

  • 예제 입력 1
2
5 6
0 0 1 0
  • 예제 출력 1
30
30
  • 예제 입력 2
3
3 4 5
1 0 1 0
  • 예제 출력 2
35
17
  • 예제 입력 3
6
1 2 3 4 5 6
2 1 1 1
  • 예제 출력 3
54
-24

Solution

#include <iostream>

using namespace std;
int maximum = -98765432;
int minimum = 98765432;
int N;
int operands[11];
int operators[4];

void DFS(int result, int idx){
    if(idx == N){
        maximum = max(maximum, result);
        minimum = min(minimum, result);
        return;
    }
    for(int i = 0; i < 4; i++){
        if(operators[i] > 0){
            operators[i]--;
            if(i == 0) DFS(result + operands[idx], idx + 1);
            else if(i == 1) DFS(result - operands[idx], idx + 1);
            else if(i == 2) DFS(result * operands[idx], idx + 1);
            else DFS(result / operands[idx], idx + 1);
            operators[i]++;
        }
    }
    return;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> N;
    for(int i = 0; i < N; i++) cin >> operands[i];
    for(int i = 0; i < 4; i++) cin >> operators[i];
    DFS(operands[0],1);
    cout << maximum << '\n';
    cout << minimum << '\n';
    return 0;
}

DFS를 활용한 백트래킹 문제다. 백트래킹이기 때문에 서치가 끝나고 나면 조건들을 원상복구 해준다.
이번 상황에서 조건은 연산자를 총 몇개를 사용했냐는 것이다.

int maximum = -98765432;
int minimum = 98765432;
int N;
int operands[11];
int operators[4];

void DFS(int result, int idx){
    if(idx == N){
        maximum = max(maximum, result);
        minimum = min(minimum, result);
        return;
    }
    for(int i = 0; i < 4; i++){
        if(operators[i] > 0){
            operators[i]--;
            if(i == 0) DFS(result + operands[idx], idx + 1);
            else if(i == 1) DFS(result - operands[idx], idx + 1);
            else if(i == 2) DFS(result * operands[idx], idx + 1);
            else DFS(result / operands[idx], idx + 1);
            operators[i]++;
        }
    }
    return;
}

총 N번의 연산을 다 마쳤다면 계산 값 중에서 최대, 최솟값이 있는 지 확인한다. 그렇지만 그게 아니라면 4 가지 경우(사칙연산) 에 대해서 모두 탐색을 시작한다.
단, 특정 시점에서 탐색이 끝나면 특정 시점에서 사용한 연산자를 다시 돌려놔야 한다. 그래야 진짜 모든 경우에 대해서 탐색이 가능하기 때문이다.
예를 들어 첫번째 시점에서 사칙연산을 모두 사용했다면, 다음 시점의 탐색에서 지장이 생길 수 있다. 한 시점에는 하나의 연산자만 사용해야 하고 이 탐색의 특성 상 한 시점에서 네 가지 사칙연산을 모두 탐색한다. 즉 DFS가 끝난 시점에서 다시 연산자를 되돌려 놓아야 한다.

재귀 함수에 대해 조금 이해를 할 필요가 있는 문제다. DFS를 활용한 백트래킹 문제는 상당히 자주 나오고 유용한 문제다. 실제로 이 블로그에서도 몇 가지 다뤘던 기억이 난다.
https://programmers.co.kr/learn/courses/30/lessons/43165
비슷한 유형이지만 좀 더 쉬운 문제다. 이 문제가 조금 더 응용되었다고 생각하면 편하다.

profile
블로그 이전합니다 : https://swj-techblog.vercel.app/

0개의 댓글