본문 바로가기
알고리즘(코딩테스트)

백준 14888번 연산자 끼워넣기(JAVA)

qbang 2021. 9. 24.
 

14888번: 연산자 끼워넣기

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

www.acmicpc.net

문제

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억보다 작거나 같다.

 

입출력 케이스

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

 

힌트

세 번째 예제의 경우에 다음과 같은 식이 최댓값/최솟값이 나온다.

  • 최댓값: 1-2÷3+4+5×6
  • 최솟값: 1+2+3÷4-5×6

 

풀이

import java.io.*;
import java.util.*;

public class Main {
	static int N;
	static int[] arr;
	static int[] num;
	static int MIN = Integer.MAX_VALUE; //최소값을 최댓값으로 초기화
	static int MAX = Integer.MIN_VALUE; //최대값을 최소값으로 초기화
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		N = Integer.parseInt(st.nextToken());
		arr = new int[N]; //주어지는 피연산자를 저장할 배열
		num = new int[4]; //주어지는 연산자(덧셈, 뺄셈, 곱셈, 나눗셈)의 개수를 저장할 배열 
		st = new StringTokenizer(br.readLine());
		for(int i=0; i<N; ++i) 
			arr[i] = Integer.parseInt(st.nextToken()); //피연산자 입력
		st = new StringTokenizer(br.readLine());
		for(int i=0; i<4; ++i) 
			num[i] = Integer.parseInt(st.nextToken()); //연산자 개수 입력
		for(int i=0; i<4; ++i) {
			int[] param = new int[N-1]; //각 자리수에 들어갈 연산(덧셈 0, 뺄셈 1, 곱셈 2, 나눗셈 3)
			param[0] = i; //제일 처음 들어갈 연산자 선택
			func(1, param);
		}
		
		System.out.println(MAX);
		System.out.println(MIN);
	}
	
	//idx는 자리수, param은 각 자리에 쓰인 연산자의 번호
	static void func(int idx, int[] param) {
		if(idx == N-1) {
			//param은 각 차수당 들어간 연산자의 번호
			//연산자의 개수가 맞는지 확인해주어야 한다
			int[] temp = new int[4];
			for(int i=0; i<N-1; ++i) {
				temp[param[i]] += 1; //각 연산자에 해당하는 위치(인덱스)의 값을 +1
			}
            
			boolean check = true;
			for(int i=0; i<4; ++i) {
				//탐색하는 동안 쓰인 연산자의 개수와 처음에 주어진 연산자의 개수와 다르면 MIN, MAX를 구하지 않는다
				if(temp[i] != num[i]) { 
					check = false;
					break;
				}
			}
			
			//연산자 개수가 일치한다면 MIN, MAX를 구함
			if(check) cal(param);
			
			return;
		}
		
		// idx 자리에 들어갈 연산자를 고른다
		for(int i=0; i<4; ++i) {
			int[] temp = param;
			temp[idx] = i; //연산자 선택
			func(idx + 1, temp); //다음 차수로 이동
		}
	}
	
	static void cal(int[] param) {
		int ans = arr[0];
		
		for(int i=0; i<N-1; ++i) {
			if(param[i] == 0) ans += arr[i+1];
			else if(param[i] == 1) ans -= arr[i+1];
			else if(param[i] == 2) ans *= arr[i+1];
			else ans /= arr[i+1];
		}
		
		MIN = Math.min(MIN, ans);
		MAX = Math.max(MAX, ans);
	}
}

 

다른 풀이

	public static void dfs(int num, int idx) {
		if (idx == N) {
			MAX = Math.max(MAX, num);
			MIN = Math.min(MIN, num);
			return;
		}
 
		for (int i = 0; i < 4; i++) {
			// 연산자 개수가 1개 이상인 경우
			if (operator[i] > 0) {
 
				// 해당 연산자를 1 감소시킨다.
				operator[i]--;
 
				switch (i) {
 
				case 0:	dfs(num + number[idx], idx + 1);	break;
				case 1:	dfs(num - number[idx], idx + 1);	break;
				case 2:	dfs(num * number[idx], idx + 1);	break;
				case 3:	dfs(num / number[idx], idx + 1);	break;
 
				}
				// 재귀호출이 종료되면 다시 해당 연산자 개수를 복구한다.
				operator[i]++;
			}
		}
	}

처음에 입력받은 연산자 배열에서 연산자 개수가 1개 이상이면 해당 연산자를 감소시키고 다시 함수를 호출한다. 이후 연산자 개수를 복구하는데, 다른 조합을 만들어내기 위해서 값을 복구하는 것이다.

다른 풀이에서와 같이 백트래킹을 이용하는 것이 메모리, 시간 복잡도 측면에서 더욱 효율적이다.

 

참고

댓글