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);
}
}
}

+ Recent posts