알고리즘 문제

백준 18233 러버덕을 사랑하는 모임 java (brute force, bit masking)

veryhi 2022. 9. 27. 02:19

https://www.acmicpc.net/problem/18233

 

18233번: 러버덕을 사랑하는 모임

첫 번째 줄에 정수 N, P, E가 공백으로 구분되어 주어진다. (1 ≤ N, P ≤ 20, 1 ≤ E ≤ 1,000,000) 그 다음 줄부터 N개의 줄에 걸쳐 회원 1부터 순서대로 xi와 yi가 공백으로 구분되어 주어진다. (1 ≤ xi 

www.acmicpc.net

 

이번 문제는 사실 저번에 백트래킹으로 풀었던 적이 있는 문제다.

 

태그에 비트마스킹이 걸려 있어서 다른 접근으로 다시 풀었다. 고로 좋은 문제 (?)

 

 

 

아래는 백트래킹으로 접근한 방식

 

https://verycrazy.tistory.com/126

 

백준 (BOJ) 18233 러버덕을 사랑하는 모임 java 백트래킹

https://www.acmicpc.net/problem/18233 18233번: 러버덕을 사랑하는 모임 첫 번째 줄에 정수 N, P, E가 공백으로 구분되어 주어진다. (1 ≤ N, P ≤ 20, 1 ≤ E ≤ 1,000,000) 그 다음 줄부터 N개의 줄에 걸쳐..

verycrazy.tistory.com

 

 

 

 

그리고 비트마스킹으로 풀었기 때문에 바로 이전 포스트에서 사용했던 코드를 재사용하고자 했다.

 

재사용이 가능한 문제여서 썼을 뿐 언제든 불가능할 수 있다 ㅎ...

 

비트 마스킹 개념이 모호해졌다면, 다시 한번 보고 오자.

 

https://verycrazy.tistory.com/142?category=1052323 

 

백준 2961 도영이가 만든 맛있는 음식 java (brute force, bit masking)

https://www.acmicpc.net/problem/2961 2961번: 도영이가 만든 맛있는 음식 첫째 줄에 재료의 개수 N(1 ≤ N ≤ 10)이 주어진다. 다음 N개 줄에는 그 재료의 신맛과 쓴맛이 공백으로 구분되어 주어진다. 모든 재

verycrazy.tistory.com

 

 

 

1. 비트마스킹을 통해 불 들어온 사람들만 따로 골라내 수를 센다. count에 저장.

for(int j=0; j<n; ++j){
    if((i & bits[j]) > 0){
        people[count] = j;
        ++count;
    }
}

 

 

 

2. 그리고 만약 카운팅한 사람 수와 목표 사람 수 p가 일치할 때 (count == p) 정답을 찾는 로직을 실행한다.

if(count == p){
    int leftSum=0, rightSum=0;
    for(int j=0; j<p; ++j){
        leftSum += ranges[people[j]][0];
        rightSum += ranges[people[j]][1];
    }

    if(leftSum <= e && e <= rightSum){
        ans = new int[n];
        solveOrNot = true;
        for(int j=0; j<p; ++j){
            ans[people[j]] = ranges[people[j]][0];
        }

        int amount = e - leftSum;
        int idx = 0;
        while (amount != 0){
            int diff = ranges[people[idx]][1] - ranges[people[idx]][0];
            if(diff <= amount){
                amount -= diff;
                ans[people[idx]] += diff;
            }
            else {
                ans[people[idx]] += amount;
                amount = 0;
            }
            ++idx;
        }

        for(int j=0; j<n; ++j){
            sb.append(ans[j]).append(" ");
        }
        break;
    }
}

정해진 사람 수만큼 나눠줘야 하기 때문에 무조건 같아야 한다. 

 

 

 

 

3. 미리 저장해두었던 people 인덱스 번호를 사용해서,

각 사람들의 left 범위값, right 범위값에 대한 총합을 구하고 e가 그 범위에 포함될 때 로직을 계속 진행

for(int j=0; j<p; ++j){
    leftSum += ranges[people[j]][0];
    rightSum += ranges[people[j]][1];
}

if(leftSum <= e && e <= rightSum){

	~ 계속 진행 ~

}

이 범위에 대한 값까지 뚫으면 정답을 찾은 것과 마찬가지이다. 한글로 풀어서 설명하면,

 

목표한 사람 수 p만큼 나눠줄 수 있고,

 

그 사람들에 대해 최소 개수 <= 나눠 주려는 총 개수 e <=  최대 개수 를 만족한 것이기 때문이다.

 

그래서 이제는 그냥 나눠주기만 하면 된다. 따라서 solveOrNot 플래그에 정답 체크를 미리 해둔다.

 

 

 

4. 먼저 최소치를 각 저장된 번호의 사람들에게 모두 나누어 준다. ans에 미리 저장한다.

for(int j=0; j<p; ++j){
    ans[people[j]] = ranges[people[j]][0];
}

 

 

 

5. 그러면 e - leftSum의 값만 남게 된다. 이 amount를 0번 사람부터 접근해서 알맞게 배분해주자.

int amount = e - leftSum;
int idx = 0;
while (amount != 0){
    int diff = ranges[people[idx]][1] - ranges[people[idx]][0];
    if(diff <= amount){
        amount -= diff;
        ans[people[idx]] += diff;
    }
    else {
        ans[people[idx]] += amount;
        amount = 0;
    }
    ++idx;
}

다만 그리디하게 매번 사람들을 만날 때마다 그 사람이 받을 수 있는 남은 범위에 대한 최대치까지 준다.

 

 

 

6. 정답을 sb에 저장시키고 출력한다. 끝.

for(int j=0; j<n; ++j){
    sb.append(ans[j]).append(" ");
}
break;
if(solveOrNot) System.out.print(sb);
else System.out.print(-1);

 

 

 

# 최종 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int p = Integer.parseInt(st.nextToken());
        int e = Integer.parseInt(st.nextToken());

        int[][] ranges = new int[n][2];
        for(int i=0; i<n; ++i){
            st = new StringTokenizer(br.readLine());
            ranges[i][0] = Integer.parseInt(st.nextToken());
            ranges[i][1] = Integer.parseInt(st.nextToken());
        }

        int[] bits = new int[n];
        for(int i=0; i<n; ++i){
            bits[i] = 1 << i;
        }

        // bit masking (비트마스킹으로) + brute force (모든 경우의 수를 뽑아본다.) + greedy (뽑힌 사람들에게 최대치로 나누어주는 방향)
        int lth = 1 << n;
        int[] ans;
        boolean solveOrNot = false;
        StringBuilder sb = new StringBuilder();
        for(int i=0; i<lth; ++i){
            int count = 0;
            int[] people = new int[n];
            for(int j=0; j<n; ++j){
                if((i & bits[j]) > 0){
                    people[count] = j;
                    ++count;
                }
            }

            if(count == p){
                int leftSum=0, rightSum=0;
                for(int j=0; j<p; ++j){
                    leftSum += ranges[people[j]][0];
                    rightSum += ranges[people[j]][1];
                }

                if(leftSum <= e && e <= rightSum){
                    ans = new int[n];
                    solveOrNot = true;
                    for(int j=0; j<p; ++j){
                        ans[people[j]] = ranges[people[j]][0];
                    }

                    int amount = e - leftSum;
                    int idx = 0;
                    while (amount != 0){
                        int diff = ranges[people[idx]][1] - ranges[people[idx]][0];
                        if(diff <= amount){
                            amount -= diff;
                            ans[people[idx]] += diff;
                        }
                        else {
                            ans[people[idx]] += amount;
                            amount = 0;
                        }
                        ++idx;
                    }

                    for(int j=0; j<n; ++j){
                        sb.append(ans[j]).append(" ");
                    }
                    break;
                }
            }
        }

        if(solveOrNot) System.out.print(sb);
        else System.out.print(-1);

    }
}

당연히 속도는 백트래킹이 훨씬 빠르다.