https://www.acmicpc.net/problem/17610
17610번: 양팔저울
무게가 서로 다른 k개의 추와 빈 그릇이 있다. 모든 추의 무게는 정수이고, 그릇의 무게는 0으로 간주한다. 양팔저울을 한 번만 이용하여 원하는 무게의 물을 그릇에 담고자 한다. 주어진 모든 추
www.acmicpc.net
1. 문제의 유형 및 이해
완전탐색
서로다른 K개의 추를 양팔저울을 한번만 비교하여 원하는 무게를 그릇에 담고자 한다.
{1,2,6}의 추가 주어질 때 , 만들 수 있는 모든 경우의 수는 다음과 같다. (ㅁ : 그릇)
그릇에 4만큼의 무게를 담고자 할때는 가지고 있는 추 2, 6을 이용하여
(ㅁ + 2) = 6 을 통해 ㅁ = 4 를 만들어 낼 수 있다.
모든 추의 합이 S라고 할 때 1~ S 중 만들지 못하는 경우의 수를 찾아보자.
처음 문제를 볼 때, '부분 합' 이 먼저 떠올랐습니다.
K= 13으로 모든 부분합 개수는 $2^{13}$ = 8192 가지로 충분히 구할 수 있습니다.
처음 푼 풀이와 풀고 나서 알게 된 풀이를 포스팅하고자 합니다.
2. 문제 접근 방법
처음 접근 방법
구하고자 하는 값을 생각해보면 [그릇 + X 부분합] : [S-X의 부분합] 으로 모든 그릇의 개수를 구할 수 있다고 생각했습니다.
집합의 경우 bit연산을 통해 충분히 계산가능한 크기라고 생각했습니다.
0110 이라고 한다면 1을 포함하지 않는 부분합의 크기들에 대해 모두 구하여 그릇의 크기를 구하였습니다.
[0110] + 그릇 = [1001] [1000] [0001] [0000] 이라고 할 때
그릇이 담을 수 있는 크기는 S-X의 부분합 - X의 부분합 으로 접근하였습니다.
이 때 그릇이 담을 수 있는 크기는 중복이 될 수 있으므로 set연산을 통해 중복을 제거하였습니다.
부분합을 담을 배열 arr을 arr[ 1<<N]만큼 정해두고 부분합마다의 값을 기록해두고 계산 하였습니다.
package DayByDay._0324;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
/**
* @문제번호 : Q17610
* @문제이름 : 양팔저울
* @난이도 : Silver I
* @date : 2022-03-24 오후 2:49
* @author : pcrmcw0486
* @문제이해
* 무게가 서로 다른 k개의 추와 빈 그릇이 있다.
* 모든 추의 무게는 정수이고 그릇의 무게는 0이다.
* 양팔 저울을 한번만 이용하여 원하는 무게의 물을 그릇에 담으려고 한다.
* @알고리즘
* 완전탐색
* @접근방법
* 양팔저울 한번을 통해 잴 수 있는 무게는
* 어떤 추들의 합 x에 대해 X와 S-X를 만들어 낼 수 있다.
* 어떤 추들의 합X는 추 집합 K의 부분 집합들이다.
* K가 13개가 주어질 때, 1~S중 양팔 저울을 한번만 이용하여 측정이 불가능한 경우의 수를 찾아보자.
* 불가능한 경우의 수 구하기.
* 정해진 정답의 범위는 1~S
* k가 13일 때 나올 수 있는 부분집합 개수는 2^13 = 8192개이다.
* 근데 과연 다 구하는게 좋을까? 최적화할 수 있는 방법은 없을 까?
* 절반만 뽑으면 모든 경우의수를 보는 것과 같다. X S-X이기 때문에.
* 그리고 같을 수도 있다.S의 최대크기는 2,600,000
*
* 조금 더 쉽게 생각하면 왼쪽에 올린다 오른쪽에 올린다 안올린다 3가지 상태...
*/
public class Main {
static int N, S, cnt, TOT;
static int[] data;
static int[] arr;
static boolean[] pos;
static Set<Integer> set;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
N = Integer.parseInt(br.readLine()); S=0;
data = new int[N];
TOT =1<<N;
arr = new int[TOT];
set = new HashSet<>();
st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++) {
data[i] = Integer.parseInt(st.nextToken());
S += data[i];
}
arr[TOT-1] = S;
pos = new boolean[S+1];
solve(0,0,0);
for (int i = 1; i < TOT; i++) {
for (int j = 0;j < TOT; j++) {
if((i & j) == 0){
set.add(Math.abs(arr[i] - arr[j]));
}
}
}
set.remove(0);
System.out.println(S-set.size());
}
private static void solve(int depth, int value, int status) {
if( depth == N){
arr[status] = value;
arr[status^(TOT-1)] = S-value;
return;
}
solve(depth+1,value,status);
solve(depth+1,value+ data[depth],(status | (1<<depth)));
}
private static void dfs(int depth, int weight){
if(depth == N){
if(weight > 0){
pos[weight]= true;
}
return;
}
dfs(depth+1, weight);
dfs(depth + 1, weight + data[depth]);
dfs(depth + 1, weight - data[depth]);
}
}
이 방법의 경우 [2+ㅁ] = 6 과 [6+ㅁ] = 2와 같이 중복되는 연산이 매우 많이 들어가게 됩니다.
N이 6일 때만 해도
[{1,2,3} + ㅁ] = ${N}_C_{6-3}$ 의 횟수 만큼 반복되는 걸 또 두번씩 하게 되기 때문에 시간이 오래 걸립니다.
이후의 풀이
처음에 접근했던 것을 다시 생각해보면 [X 부분합] + ㅁ = [S-X의 부분합]을 구하고 싶었습니다.
이때 ㅁ의 크기는 {S-X}부분합 - {X}의 부분합 으로 정할 수 있구요.
S-X를 오른쪽 저울, X를 왼쪽 저울이라고 생각하면
그렇다면 우리는 배열을 돌면서 왼쪽 저울에 올릴지, 오른쪽 저울에 올릴지만 정하면 됩니다.
이 때 한번 더 생각해야할 것은, 저울에 올리지 않는 경우까지도 생각해주면됩니다.
즉, 구하고자 하는 weight값은
weight + 추(오른쪽 저울에 올린다)
weight - 추 (왼쪽 저울에 올린다)
weight( 안 올린다)
의 모든 경우의 수를 구해주게 되면 됩니다.
위 코드의 dfs함수를 참조하시면 됩니다.
집합을 나누어 각 집합에 넣어주는 행위에 대해 잘 생각해보면 좋을것 같습니다.
'알고리즘 > 문제풀이' 카테고리의 다른 글
[백준] Q22866 탑 보기JAVA (0) | 2022.03.25 |
---|---|
[백준] Q11085 군사 이동 JAVA (0) | 2022.03.24 |
[백준] Q9079 동전게임 JAVA (0) | 2022.03.24 |
[백준] Q2204 도비의 난독증 테스트 JAVA (0) | 2022.03.23 |
[백준] Q16566 카드게임 JAVA (0) | 2022.03.08 |