#2629 양팔저울
난이도 : 골드 2
유형 : DP / 배낭문제(조합최적화)
▸ 문제
양팔 저울과 몇 개의 추가 주어졌을 때, 이를 이용하여 입력으로 주어진 구슬의 무게를 확인할 수 있는지를 결정하려고 한다.
무게가 각각 1g과 4g인 두 개의 추가 있을 경우, 주어진 구슬과 1g 추 하나를 양팔 저울의 양쪽에 각각 올려놓아 수평을 이루면 구슬의 무게는 1g이다. 또 다른 구슬이 4g인지를 확인하려면 1g 추 대신 4g 추를 올려놓으면 된다.
구슬이 3g인 경우 아래 <그림 1>과 같이 구슬과 추를 올려놓으면 양팔 저울이 수평을 이루게 된다. 따라서 각각 1g과 4g인 추가 하나씩 있을 경우 주어진 구슬이 3g인지도 확인해 볼 수 있다.
<그림 2>와 같은 방법을 사용하면 구슬이 5g인지도 확인할 수 있다. 구슬이 2g이면 주어진 추를 가지고는 확인할 수 없다.
추들의 무게와 확인할 구슬들의 무게가 입력되었을 때, 주어진 추만을 사용하여 구슬의 무게를 확인 할 수 있는지를 결정하는 프로그램을 작성하시오.
▸ 입력
첫째 줄에는 추의 개수가 자연수로 주어진다. 추의 개수는 30 이하이다. 둘째 줄에는 추의 무게들이 자연수로 가벼운 것부터 차례로 주어진다. 같은 무게의 추가 여러 개 있을 수도 있다. 추의 무게는 500g이하이며, 입력되는 무게들 사이에는 빈칸이 하나씩 있 다. 세 번째 줄에는 무게를 확인하고자 하는 구슬들의 개수가 주어진다. 확인할 구슬의 개수는 7이하이다. 네 번째 줄에는 확인하고자 하는 구슬들의 무게가 자연수로 주어지며, 입력되는 무게들 사이에는 빈 칸이 하나씩 있다. 확인하고자 하는 구슬의 무게는 40,000보다 작거나 같은 자연수이다.
▸ 출력
주어진 각 구슬의 무게에 대하여 확인이 가능하면 Y, 아니면 N 을 차례로 출력한다. 출력은 한 개의 줄로 이루어지며, 각 구슬에 대한 답 사이에는 빈칸을 하나씩 둔다.
문제 풀이
해당 문제는 주어진 추의 무게로 구슬의 무게를 확인할 수 있는지 여부를 판단하는 것이다. 해당 문제의 구슬은 쪼갤 수 없으므로 0-1 배낭문제(Knapsack Problem) 종류의 문제이다.
(* 참고 : 쪼갤 수 있다면 Fractional 배낭문제이다.)
추의 개수 2개, 추의 무게 1g, 4g일 때 ,구할 수 있는 구슬의 무게는 다음과 같다.
ex) 1g, 4g, 1+4 = 5g, |1-4| = 3g 총 4개의 무게가 나온다
점화식으로는 상위 문제를 조합해나가는 것이 익숙치않아 재귀호출탐색 + 메모이제이션으로 조합하는 방식을 택했다.
이를 어떻게 구해야할까?
0-1 배낭문제보다는 간단하다. 배낭문제는 max가 정해지고 그 한도 내에 넣을 수 있는 최대의 value를 골라내는 DP문제라면 이 문제는 그냥 추들이 낼 수 있는 숫자를 모두 조합하여 저장해주면 된다.
- 0< 추 무게 < 500 * 30 = 15,000
- 0< 구슬 무게 <40,000
추의 개수가 n개일 때는 추의 무게를 각 f(0), f(1), ... , f(n-1)이라고 한다면 구할 수 있는 구슬의 무게는 다음과 같다.
1) 기본적으로 f(0)g, .... , f(n-1)g n개의 무게를 구할 수 있다.
→ 그리고 여러 개의 추를 합침으로써 새로운 무게를 잴 수 있다.
sum{ f(0) ~ f(n-1) 2개 조합 } ;
sum{ f(0) ~ f(n-1) 3개 조합 } ;
~
sum{ f(0) ~ f(n-1) n-1개 조합 } ;
→ 마지막으로 여러 개의 추를 뺌으로써 새로운 무게를 잴 수 있다. (반대쪽 저울에 추를 올리면 된다.)
sub{ f(0) ~ f(n-1) 2개 조합 } ;
sub{ f(0) ~ f(n-1) 3개 조합 } ;
~
sub{ f(0) ~ f(n-1) n-1개 조합 } ;
따라서 추의 무게 조합 방법은 총 3가지임을 알 수 있다.
- 추 하나 그대로 무게
- 현재 추의 무게에 새로운 추 더하기 (구슬 <-> 추+새로운 추)
- 현재 추의 무게에 반대편에 새로운 추 더해서 총 무게 빼기 (구슬+새로운 추 <-> 추)
이를 재귀호출탐색 방식으로 코딩하면 다음과 같다.
static void dp(int cnt, int num) {
if(result[cnt][num]) return;
result[cnt][num] = true;
if(cnt == n) return;
dp(cnt+1, num+ w[cnt]);
dp(cnt+1, num);
dp(cnt+1, Math.abs(num- w[cnt]));
}
풀이 코드
// #2629 dp topdown 양팔저울
import java.io.*;
import java.util.*;
public class TwoArmScales {
static int n;
static int[] w;
static boolean[][] result;
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(br.readLine());
StringTokenizer st = new StringTokenizer(br.readLine());
w = new int[n];
result = new boolean[n+1][40001];
for(int i=0; i<n; i++) {
int src = Integer.parseInt(st.nextToken());
w[i] = src;
}
dp(0,0);
for(int j=0; j<20; j++) {
if(result[n][j]) {
System.out.println(j);
System.out.print(result[n][j]+" ");
}
}
int c = Integer.parseInt(br.readLine());
StringBuilder sb = new StringBuilder();
st = new StringTokenizer(br.readLine());
for(int i=0; i<c; i++) {
int t = Integer.parseInt(st.nextToken());
if(result[n][t]) {
sb.append("Y ");
}else {
sb.append("N ");
}
}
System.out.println(sb.toString());
}
static void dp(int cnt, int num) {
if(result[cnt][num]) return;
result[cnt][num] = true;
if(cnt == n ) return;
dp(cnt+1, num+ w[cnt]);
dp(cnt+1, num);
dp(cnt+1, Math.abs(num- w[cnt]));
}
}
+ 다른 분 참고한 점화식 코드 (링크)
//조합
for(int i=1;i<=N;i++) {
ArrayList<Integer> temp = new ArrayList<Integer>();
temp.add(ball[i]);
for(int j=1;j<=15000;j++) {
if(isAvail[j]) {
if(ball[i]-j > 0) {
temp.add(ball[i]-j);
}
if(ball[i]+j <= 15000) {
temp.add(ball[i]+j);
}
if(j-ball[i] > 0) {
temp.add(j-ball[i]);
}
}
}
for(int j=0;j<temp.size();j++) {
isAvail[temp.get(j)] = true;
}
}
'Dot Algo∙ DS > PS' 카테고리의 다른 글
[BOJ] 백준 3055번 탈출 (Java) (0) | 2021.05.04 |
---|---|
[프로그래머스] 2021 카카오/ 신규 아이디 추천 (Java) (0) | 2021.05.03 |
[BOJ] 백준 1922번 네트워크 연결 (Java) (0) | 2021.05.03 |
[BOJ] 백준 10835번 카드게임 (Java) (0) | 2021.05.02 |
[BOJ] 백준 1562번 계단 수 (Java) (0) | 2021.05.02 |