https://www.acmicpc.net/problem/1535
1535번: 안녕
첫째 줄에 사람의 수 N(≤ 20)이 들어온다. 둘째 줄에는 각각의 사람에게 인사를 할 때, 잃는 체력이 1번 사람부터 순서대로 들어오고, 셋째 줄에는 각각의 사람에게 인사를 할 때, 얻는 기쁨이 1번
www.acmicpc.net
코드설명
냅색(배낭, Knapsack) + DP(Dynamic Programming)문제입니다.
문제는 냅색 배낭 알고리즘입니다.
N의 범위가 작기때문에 DFS를 활용하여 해결했습니다.
문제에는 DP없이 사용하여도 해결할 수 있지만, N이 커진다면 어떻게 될까 하여 DP를 같이 적용해보았습니다.
코드
simulate에 happy값을 넣어서 계산하지 않고, 계산 도중에 happy 값을 처리합니다.
추가로 DP[level][health] : 해당 level, health에서의 최대 행복값을 저장합니다.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Main { public static int N, M; public static int answer = 0; public static int[][] DP; public static int[] L; //체력 public static int[] J; //기쁨 public static int health = 100; 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()); DP = new int[21][101]; //21은 인사해야할 사람의 숫자, 101 세준이의 체력의 최대값을 의미합니다. 그리고 DP값은 기쁨의 최대입니다. L = new int[N]; J = new int[N]; st = new StringTokenizer(br.readLine()); for(int i=0;i<N;i++) { L[i] = Integer.parseInt(st.nextToken()); } st = new StringTokenizer(br.readLine()); for(int i=0; i<N;i++) { J[i] = Integer.parseInt(st.nextToken()); } answer = simulate(0, 0); System.out.println(answer); } public static int simulate(int level, int healthValue) { if(DP[level][healthValue] > 0) return DP[level][healthValue]; if(level == N) return 0; //범위를 벗어납니다. int J1 = 0; //기쁨정도 if(healthValue + L[level] < health) { //만약 최대체력범위 100 미만이라면 J1 = J[level] + simulate(level + 1, healthValue + L[level]); //해당 즐거움을 더하는경우 } int J2 = simulate(level + 1, healthValue + 0); //해당 경우를 더하지 않은경우 return DP[level][healthValue] = Math.max(J1, J2); } }
simulate인자에 level과 health 값을 넣어서 계산합니다.
완전탐색을 통한 로직입니다.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Main { public static int N, M; public static Node[] node; public static int answer = 0; 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()); node = new Node[N]; for(int i=0;i<N;i++) { node[i] = new Node(); } st = new StringTokenizer(br.readLine()); for(int i=0;i<N;i++) { int input = Integer.parseInt(st.nextToken()); node[i].health = input; } st = new StringTokenizer(br.readLine()); for(int i=0;i<N;i++) { int input = Integer.parseInt(st.nextToken()); node[i].happy = input; } answer = simulate(0, 100); System.out.println(answer); } public static int simulate(int level, int health) { if(level >= N) { return 0; } int resultA = 0, resultB = 0; Node temp = node[level]; if(health - temp.health > 0) { resultA = temp.happy + simulate(level + 1, health - temp.health); } resultB = simulate(level + 1, health); return Math.max(resultA, resultB); } } class Node{ int health; int happy; public Node(int health, int happy) { this.health = health; this.happy = happy; } public Node() { } }
아래의 코드에 DP를 추가해보려고 한 코드이지만, DP[level][health][happy] : 현재 Level에서 해당 Health를 가지고 있을떄 가질 수 있는 최대 행복값을 구해야합니다. (만약 위의 코드와 같이 level, healthValue가 함수의 인자로 들어가 있다면, DP[level][health] 로 처리해야합니다.)
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Main { public static int N, M; public static Node[] node; public static int answer = 0; public static int[][][] dp; 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()); node = new Node[N]; dp = new int[21][100 + 1][20*100 + 1]; for(int i=0;i<N;i++) { node[i] = new Node(); } st = new StringTokenizer(br.readLine()); for(int i=0;i<N;i++) { int input = Integer.parseInt(st.nextToken()); node[i].health = input; } st = new StringTokenizer(br.readLine()); for(int i=0;i<N;i++) { int input = Integer.parseInt(st.nextToken()); node[i].happy = input; } answer = simulate(0, 100, 0); System.out.println(answer); } public static int simulate(int level, int health, int happy) { if(dp[level][health][happy] > 0) { return dp[level][health][happy]; } if(level >= N || health < 0 ) { return happy; } int resultA = 0, resultB = 0; Node temp = node[level]; //해당 level의 사람에게 인사하는 경우. ( 체력이 남아있어야한다. 처음에 0 인경우까지 포함했는데 체력이 0 이면 인사를 못합니다. ) if(health - temp.health > 0) { resultA = simulate(level + 1, health - temp.health, happy + temp.happy); } //해당 level의 사람에게 인사하지 않는 경우 resultB = simulate(level + 1, health, happy); return dp[level][health][happy] = Math.max(resultA, resultB); } } class Node{ int health; int happy; public Node(int health, int happy) { this.health = health; this.happy = happy; } public Node() { } }
단순히 완전탐색을 통해서 해결한 코드입니다.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Main { public static int N, M; public static Node[] node; public static int answer = 0; 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()); node = new Node[N]; for(int i=0;i<N;i++) { node[i] = new Node(); } st = new StringTokenizer(br.readLine()); for(int i=0;i<N;i++) { int input = Integer.parseInt(st.nextToken()); node[i].health = input; } st = new StringTokenizer(br.readLine()); for(int i=0;i<N;i++) { int input = Integer.parseInt(st.nextToken()); node[i].happy = input; } answer = simulate(0, 100, 0); System.out.println(answer); } public static int simulate(int level, int health, int happy) { if(level >= N || health < 0 ) { return happy; } int resultA = 0, resultB = 0; Node temp = node[level]; //해당 level의 사람에게 인사하는 경우. ( 체력이 남아있어야한다. ) if(health - temp.health > 0) { resultA = simulate(level + 1, health - temp.health, happy + temp.happy); } //해당 level의 사람에게 인사하지 않는 경우 resultB = simulate(level + 1, health, happy); return Math.max(resultA, resultB); } } class Node{ int health; int happy; public Node(int health, int happy) { this.health = health; this.happy = happy; } public Node() { } }
메모이제이션을 적용하여 가장 빠른 코드입니다.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; import java.util.LinkedList; import java.util.Queue; import java.util.StringTokenizer; public class Main { private static int N, K, M; private static int answer = 0; private static int[] arr, L, J; private static int[][] cache; 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()); L = new int[N]; J = new int[N]; st = new StringTokenizer(br.readLine()); for(int i=0;i<N; i++) { L[i] = Integer.parseInt(st.nextToken()); } st = new StringTokenizer(br.readLine()); for(int i=0;i<N; i++) { J[i] = Integer.parseInt(st.nextToken()); } cache = new int[101][N]; for(int i=0;i<101; i++) { Arrays.fill(cache[i], -1); } System.out.println(getBestHappy(100, 0)); } public static int getBestHappy(int energy, int personIdx) { if(personIdx == N) { return 0; } if(cache[energy][personIdx] != -1) return cache[energy][personIdx]; int ret = 0; ret = getBestHappy(energy, personIdx + 1); if(energy - L[personIdx] > 0) { ret = Math.max(ret, getBestHappy(energy - L[personIdx], personIdx + 1) + J[personIdx]); } return cache[energy][personIdx] = ret; } }
만약, 내가 인사한 사람들의 최대 선호도와 함께, 인사한 사람들의 실제 목록이 궁금할경우의 코드입니다.
reconstruct 함수를 통해서 실제 어떤 사람들에게 인사하였는지 출력할 수 있습니다.
package Main; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.StringTokenizer; public class Main { private static int N, K, M; private static int answer = 0; private static int[] arr, L, J; private static int[][] cache; private static ArrayList<Integer> picked = new ArrayList<>(); 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()); L = new int[N]; J = new int[N]; st = new StringTokenizer(br.readLine()); for(int i=0;i<N; i++) { L[i] = Integer.parseInt(st.nextToken()); } st = new StringTokenizer(br.readLine()); for(int i=0;i<N; i++) { J[i] = Integer.parseInt(st.nextToken()); } cache = new int[101][N]; for(int i=0;i<101; i++) { Arrays.fill(cache[i], -1); } System.out.println(getBestHappy(100, 0)); reconstruct(100, 0); for(int v : picked) { System.out.print(v+" "); } } public static int getBestHappy(int energy, int personIdx) { if(personIdx == N) { return 0; } if(cache[energy][personIdx] != -1) return cache[energy][personIdx]; int ret = 0; ret = getBestHappy(energy, personIdx + 1); if(energy - L[personIdx] > 0) { ret = Math.max(ret, getBestHappy(energy - L[personIdx], personIdx + 1) + J[personIdx]); } return cache[energy][personIdx] = ret; } public static void reconstruct(int energy, int personIdx) { //기저사례 : 모든 사람들을 다 고려했음. if(personIdx == N) return ; //두개의 선택지밖에 없기에 아래와 같이 단순하게 처리할 수 있습니다. //만약 현재 energy와 personIdx와 energy,personIdx+1 때의 행복도가 같다면, //해당 사람에게는 인사하지 않았다는 의미입니다. ( 인사한 경우라면 [energy][personIdx]의 값은 달라졌어야하기 때문입니다. ) //만약 둘이 같다면, 인사를 하지 않아서 같습니다. 그러므로 다음 재귀함수로 넘어갑니다. if(getBestHappy(energy, personIdx) == getBestHappy(energy, personIdx + 1)) { reconstruct(energy, personIdx + 1); } //위의 경우에서 다른 사람에게 인사한 값이 최대값인걸 알았다면, //해당 사람에게는 인사한 것으로 처리하고, 다음 재귀함수를 계속해서 실행합니다. else if(energy - L[personIdx] > 0) { picked.add(personIdx); reconstruct(energy - L[personIdx], personIdx + 1); } } }