[Gold IV] 다이어트 - 19942
성능 요약
메모리: 15052 KB, 시간: 104 ms
N개의 식자재 중에 단백질, 지방, 탄수화물, 비타민의 최소 기준을 만족하고 최소 비용을 가지도록 식자재를 선택하는 문제
⭕ 접근 방법. 조합, 부분집합
그렇다면 식재료들로 조합할 수 있는 모든 조합은 어떻게 만들까 ?
재귀 함수를 이용하자.
각 식재료들은 선택할 지, 선택하지 않을 지로 나뉜다.
A 식재료를 선택한 경우 다음 B 식재료를 보고 또 선택할지 선택하지 않을지 고른다.
만약 A 식재료를 선택하지 않은 경우도 다음 B 식재료를 보고 선택할지 선택하지 않을지 고른다.
다음 C 식재료도 , D 식재료도 마찬가지 이다.
여기서 중요한 건 고른 식재료들의 영양소합과 비용합이다.
식재료를 골랐는지 고르지 않았는지에 따라 값이 달라지기 때문에 이때는 재귀함수의 파라미터로 저장하여 상태에 따른 값을 가지고 가면 된다.
private static void powerset(int foodIdx, int totalCost, int[] total) {
// foodIdx번째 식재료 선택
selectedFood.add(foodIdx);
for (int i = 0; i < 4; i++) {
total[i] += foodInfo[foodIdx][i]; // 선택된 식재료의 영양소 합 구하기
}
powerset(foodIdx + 1, totalCost + foodInfo[foodIdx][4], total); // 현재 식재료 선택하고 다음 식재료로 넘어감
// foodIdx번째 식재료 선택 안 함
selectedFood.remove(selectedFood.size() - 1); // 마지막으로 선택된 식재료 제거
for (int i = 0; i < 4; i++) {
total[i] -= foodInfo[foodIdx][i]; // 선택된 식재료의 영양소 합 갱신 (선택하지 않는 경우)
}
powerset(foodIdx + 1, totalCost, total); // 현재 식재료를 선택하지 않고 다음 식재료로 넘어감
}
그렇게 쭉쭉 재귀를 타고 들어가다가 마침내 모든 식재료를 선택할지, 선택하지 않을지 다 결정을 한다면
3가지 조건을 만족하는지 판단하면 된다.
그러나 이때 4가지 식재료 중에 2가지만 골랐음에도 최소 비용보다 비용이 더 높아지는 경우가 있다.
이런 경우 계속해서 뒤에 식재료를 고르는 걸 고려할 필요가 없다. 이미 비용이 높아졌으니 최종 비용은 같거나 더 크기 때문이다. 그래서 중간에 가지 치기를 한다.
private static void powerset(int foodIdx, int totalCost, int[] total) {
if (totalCost > minCost) return; // 조건 1. 현재 비용이 최소 비용보다 크면 중단
if (foodIdx > N) { // 모든 식재료를 고려했을 때
for (int i = 0; i < 4; i++) {
if (min[i] > total[i]) return; // 조건 2. 최소 영양소를 만족하지 못하면 중단
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < selectedFood.size(); i++) {
sb.append(selectedFood.get(i) + " "); // 선택된 식재료 번호 저장
}
if (totalCost == minCost) { // 현재 비용이 최소 비용과 같을 때
// result의 아스키코드 - sb의 아스키코드 => 음수이면 sb가 더 크다는 뜻이니까 사전순으로 뒤여야함.
if (result != null && result.compareTo(sb.toString()) < 0) return; // 조건 3. 사전순으로 더 빠른 결과가 이미 저장되어 있으면 중단
}
minCost = totalCost; // 최소 비용 갱신
result = sb.toString(); // 결과 저장
return;
}
// foodIdx번째 식재료 선택
selectedFood.add(foodIdx);
for (int i = 0; i < 4; i++) {
total[i] += foodInfo[foodIdx][i]; // 선택된 식재료의 영양소 합 구하기
}
powerset(foodIdx + 1, totalCost + foodInfo[foodIdx][4], total); // 현재 식재료 선택하고 다음 식재료로 넘어감
// foodIdx번째 식재료 선택 안 함
selectedFood.remove(selectedFood.size() - 1); // 마지막으로 선택된 식재료 제거
for (int i = 0; i < 4; i++) {
total[i] -= foodInfo[foodIdx][i]; // 선택된 식재료의 영양소 합 갱신 (선택하지 않는 경우)
}
powerset(foodIdx + 1, totalCost, total); // 현재 식재료를 선택하지 않고 다음 식재료로 넘어감
}
각 식재료에 따라서 선택함, 선택하지 않음 두가지 선택권이 있으므로
➡️ 해당 풀이법의 시간 복잡도 :
결론 : 문제를 잘 읽자 ㅎ!
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;
public class Main_19942 {
static int N; // 식재료 개수
static int min[]; // 최소로 얻어야 하는 영양소
static int foodInfo[][]; // 각 식재료의 영양 정보와 가격
static int minCost = Integer.MAX_VALUE; // 최소 비용
static ArrayList<Integer> selectedFood; // 선택된 식재료의 번호를 저장하는 리스트
static String result; // 최종 결과를 저장하는 문자열
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine()); // 식재료 개수 입력
min = new int[4]; // 최소 영양소 배열 초기화
StringTokenizer st = new StringTokenizer(br.readLine()); // 최소 영양소 입력 받기
for (int i = 0; i < 4; i++) {
min[i] = Integer.parseInt(st.nextToken());
}
foodInfo = new int[N + 1][5]; // 각 식재료의 정보를 담는 배열 초기화
for (int i = 1; i < N + 1; i++) {
st = new StringTokenizer(br.readLine()); // 식재료 정보 입력 받기
for (int j = 0; j < 5; j++) {
foodInfo[i][j] = Integer.parseInt(st.nextToken());
}
}
int total[] = new int[4]; // 선택된 식재료의 영양소 합을 저장하는 배열 초기화
selectedFood = new ArrayList<>(); // 선택된 식재료의 번호를 저장할 리스트 초기화
powerset(1, 0, total); // 조합 생성 및 검사 함수 호출
// 결과 출력
if (result == null) {
System.out.println(-1); // 조건을 만족하는 식재료가 없는 경우 -1 출력
} else {
System.out.println(minCost); // 최소 비용 출력
System.out.println(result); // 선택된 식재료 번호 출력
}
}
// powerset 함수: 조합 생성 및 검사
private static void powerset(int foodIdx, int totalCost, int[] total) {
if (totalCost > minCost) return; // 현재 비용이 최소 비용보다 크면 중단
if (foodIdx > N) { // 모든 식재료를 고려했을 때
for (int i = 0; i < 4; i++) {
if (min[i] > total[i]) return; // 최소 영양소를 만족하지 못하면 중단
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < selectedFood.size(); i++) {
sb.append(selectedFood.get(i) + " "); // 선택된 식재료 번호 저장
}
if (totalCost == minCost) { // 현재 비용이 최소 비용과 같을 때
// result의 아스키코드 - sb의 아스키코드 => 음수이면 sb가 더 크다는 뜻이니까 사전순으로 뒤여야함.
if (result != null && result.compareTo(sb.toString()) < 0) return; // 사전순으로 더 빠른 결과가 이미 저장되어 있으면 중단
}
minCost = totalCost; // 최소 비용 갱신
result = sb.toString(); // 결과 저장
return;
}
// foodIdx번째 식재료 선택
selectedFood.add(foodIdx);
for (int i = 0; i < 4; i++) {
total[i] += foodInfo[foodIdx][i]; // 선택된 식재료의 영양소 합 구하기
}
powerset(foodIdx + 1, totalCost + foodInfo[foodIdx][4], total); // 현재 식재료 선택하고 다음 식재료로 넘어감
// foodIdx번째 식재료 선택 안 함
selectedFood.remove(selectedFood.size() - 1); // 마지막으로 선택된 식재료 제거
for (int i = 0; i < 4; i++) {
total[i] -= foodInfo[foodIdx][i]; // 선택된 식재료의 영양소 합 갱신 (선택하지 않는 경우)
}
powerset(foodIdx + 1, totalCost, total); // 현재 식재료를 선택하지 않고 다음 식재료로 넘어감
}
}