문제
도영이는 짜파구리 요리사로 명성을 날렸었다. 이번에는 이전에 없었던 새로운 요리에 도전을 해보려고 한다.
지금 도영이의 앞에는 재료가 N개 있다. 도영이는 각 재료의 신맛 S와 쓴맛 B를 알고 있다. 여러 재료를 이용해서 요리할 때, 그 음식의 신맛은 사용한 재료의 신맛의 곱이고, 쓴맛은 합이다.
시거나 쓴 음식을 좋아하는 사람은 많지 않다. 도영이는 재료를 적절히 섞어서 요리의 신맛과 쓴맛의 차이를 작게 만들려고 한다. 또, 물을 요리라고 할 수는 없기 때문에, 재료는 적어도 하나 사용해야 한다.
재료의 신맛과 쓴맛이 주어졌을 때, 신맛과 쓴맛의 차이가 가장 작은 요리를 만드는 프로그램을 작성하시오.
입력
첫째 줄에 재료의 개수 N(1 ≤ N ≤ 10)이 주어진다. 다음 N개 줄에는 그 재료의 신맛과 쓴맛이 공백으로 구분되어 주어진다. 모든 재료를 사용해서 요리를 만들었을 때, 그 요리의 신맛과 쓴맛은 모두 1,000,000,000보다 작은 양의 정수이다.
출력
첫째 줄에 신맛과 쓴맛의 차이가 가장 작은 요리의 차이를 출력한다.
풀이
각 재료를 넣을지 말지를 결정하는 모든 부분집합을 확인하면 된다. 선택된 재료들의 신맛은 곱으로 누적되고 쓴맛은 합으로 누적되므로, 공집합만 제외하고 |신맛 - 쓴맛|의 최솟값을 갱신하면 된다.
현재 코드는 재귀나 비트마스크로 재료 선택 여부를 나누며 탐색한다. 문제의 핵심은 신맛이 합이 아니라 곱이라는 점이라서, 상태를 갱신할 때 두 값을 각각 다른 방식으로 관리해야 한다.
코드
import java.io.*;
import java.util.*;
public class Main {
static long ans = Long.MAX_VALUE;
static int N;
static int[][] arr;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
N = Integer.parseInt(br.readLine());
arr = new int[N][2];
for (int i = 0; i < N; i++) {
st = new StringTokenizer(br.readLine());
arr[i][0] = Integer.parseInt(st.nextToken());
arr[i][1] = Integer.parseInt(st.nextToken());
}
solve(0, 1, 0);
System.out.println(ans);
}
static void solve(int cnt, long S, long B) {
if (cnt == N) {
if (S != 0 && B != 0)
ans = Math.min(ans, Math.abs(B - S));
return;
}
solve(cnt + 1, S, B);
solve(cnt + 1, S * arr[cnt][0], B + arr[cnt][1]);
}
}복잡도
- 시간 복잡도: 재료 부분집합을 모두 확인하므로 이다.
- 공간 복잡도: 재귀 스택과 재료 배열을 사용하므로 이다.
마무리
재료 수가 작으므로 모든 선택 조합을 직접 확인할 수 있다. 신맛은 곱, 쓴맛은 합으로 누적한다는 차이만 잘 지키면 최솟값 갱신은 단순하다.
