J 크 2023. 10. 8. 14:31
728x90
반응형

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

 

17471번: 게리맨더링

선거구를 [1, 4], [2, 3, 5, 6]으로 나누면 각 선거구의 인구는 9, 8이 된다. 인구 차이는 1이고, 이 값보다 더 작은 값으로 선거구를 나눌 수는 없다.

www.acmicpc.net

달려보자


※  문제를 요약하면 아래와 같습니다.

▶ 1번 부터 N번의 구역이 존재, 이 구역들을 두 선거구로 나눠야 함

이 때 각 선거구안의 구역들은 반드시 연결 되어 있는 상태

    ex)  1~6번까지의 구역이 아래와 같이 연결되어 있을 때

▶ 각 구역에 사람의 수가 입력되어있으며, 두 선거구에 인구의 차이를 최소화 하려함

▶ 구역에 대한 정보가 주어졌을 때, 인구 차이의 최소값 탐색

▶ Input : 첫째 줄에 구역의 갯수 N, 다음 줄에 구역 별 사람 수,

                셋째 줄 부터 N개의 줄에 각 구역에 인접한 구역의수, 연결된 구역 번호가 입력

▶ Output : 두 선거구로 나누었을 때 인구 차이의 최소값 출력


◈ Input - 1

6
5 2 3 4 1 2
2 2 4
4 1 3 6 5
2 4 2
2 1 3
1 2
1 2

◈ Output - 1

1

◈ Input - 2

6
1 1 1 1 1 1
2 2 4
4 1 3 6 5
2 4 2
2 1 3
1 2
1 2

◈ Output - 2

0

◈ Input - 3

6
10 20 10 20 30 40
0
0
0
0
0
0

◈ Output - 3

-1

◈ Input - 4

6
2 3 4 5 6 7
2 2 3
2 1 3
2 1 2
2 5 6
2 4 6
2 4 5

◈ Output - 4

9

 ◎  코드 작성 전, 아래와 같이 솔루션을 정리하였습니다.

 0~N-1 (1~N) 까지 두 가지로 나눌 수 있는 모든 경우의 수 탐색

▶ 위의 경우의 수는 재귀 형태로 구현, 1~ N/2 크기 만큼 번호를 선택하여  1번 구역

     선택되지 않은 구역은 2번 구역으로 나누어 각 구역의 노드들이 연결 되어 있는지 확인\

        ex) Input- 1

               [1,4], [2,3,5,6] 은 가능한 1, 2번 구역의 노드들이 연결되어 있으므로 구역 나누기 가능

               [1,2], [3,4,5,6] 은 3,4 번과 5,6 번이 연결 되어 있지 않으므로 구역 나누기 불가능

▶ 첫번째 배열의 크기를 1부터 N/2까지 크기에 대해 전부 재귀 탐색을 진행하여 경우의 수 탐색

     ( 코드 내 nPerm 변수 ) 

    ex)  [1,2,3] / [4,5,6] 과 [4,5,6] / [1,2,3] 

           [2] / [1,3,4,5,6] 과 [1,3,4,5,6] / [2] 등등 은 그룹의 순서가 바뀌어도 구분된 노드들이 같기 때문

▶ 두 가지로 나눈 경우의 수에 대해 각 구역들이 연결되어있는지 탐색

    1. 조합에서 선택된 번호와 선택되지 않은 번호의 [0]번 인덱스를 시작으로 DFS진행 ( 코드 내 IsConnected 함수)

    2. 2번의 DFS가 끝난 후 방문되지 않은 노드 존재 == 각 선거구 내부의 노드가 서로 연결이 되어 있지 않음

         해당 경우는 선거구를 두 구역으로 나눌 수 없음

      ex) Input-1

          Case :  [1,2], [3,4,5,6]  =>  DFS로 1, 3 번 탐색 시 5,6번이 방문되지 않는다 == 연결이 되어 있지 않다.

    3. 모두 방문 했을 경우 각 선거구의 인원수를 합하여 차이를 비교, 최소값과 비교하여 저장

    4. 모든 케이스에 대하여 1~3번 반복

▶ 최소값 출력


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {

    static int N, nMin, nPerm;
    static ArrayList<ArrayList<Integer>> listNode;

    static int[] narrPeople;
    static boolean[][] barrVisit;

    static int[] narrNums;
    static int[] narrAnothers;
    static boolean[] barrNums;
    static boolean[] barrDFS;

    public static void main(String[] args) throws IOException {
        BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bWriter = new BufferedWriter(new OutputStreamWriter(System.out));

        // 노드 입력 받고
        N = Integer.parseInt(bReader.readLine());
        listNode = new ArrayList<>();
        for (int i = 0; i < N; i++) {
            listNode.add(new ArrayList<>());
        }

        narrPeople = new int[N];
        // 인구수 입력 받자
        StringTokenizer st = new StringTokenizer(bReader.readLine());
        for (int i = 0; i < N; i++)
            narrPeople[i] = Integer.parseInt(st.nextToken());

        // 구역 간의 관계를 (노드 관계) 입력 받자
        for (int i = 0; i < N; i++) {
            st = new StringTokenizer(bReader.readLine());
            int nNodeCount = Integer.parseInt(st.nextToken());
            for (int j = 0; j < nNodeCount; j++) {
                int nNextNode = Integer.parseInt(st.nextToken()) - 1;
                listNode.get(i).add(nNextNode);

            }
        }

        nMin = Integer.MAX_VALUE;
        // 일단 나눌 수 있는 경우의 수
        // 절반까지만 탐색하면 나머지 절반은 이미 나눠본적 있는 그룹이므로 탐색할 필요가 없다
        nPerm = 1;
        while (nPerm <= N / 2) {
            narrNums = new int[nPerm];
            barrNums = new boolean[N];
            narrAnothers = new int[N - nPerm];
            getCases(0);
            nPerm++;
        }

        if (nMin == Integer.MAX_VALUE)
            nMin = -1;

        bWriter.write(String.valueOf(nMin));
        bWriter.close();

    }

    private static void getCases(int nIndex) {

        // 기저 조건
        if (nIndex == nPerm) {
            // 두가지 케이스로 나눴을 경우
            // 각 케이스 안의 노드들 끼리 연결 되있는지 확인해보자
            // 조합에 뽑히지 않은 나머지
            int nAnotherIdx = 0;
            for (int i = 0; i < N; i++) {
                if (!barrNums[i])
                    narrAnothers[nAnotherIdx++] = i;
            }
            
            // 두 개로 나눈 노드의 그룹이 연결되어있다면
            // 사람 수 차이를 비교해보자
            if (IsConnected()) {
                int nSumFirst = 0;
                int nSumSecond = 0;

                for (int i = 0; i < nPerm; i++)
                    nSumFirst += narrPeople[narrNums[i]];
                for (int i = 0; i < N - nPerm; i++)
                    nSumSecond += narrPeople[narrAnothers[i]];

                int nResult = Math.abs(nSumFirst - nSumSecond);

                // 인구 차이가 최소인 것 받아놓자
                if (nMin > nResult) {
                    nMin = nResult;
                }
            }
            return;
        }

        // 재귀 조건
        // 일단 조합 뽑자
        for (int i = nIndex; i < N; i++) {
            if (!barrNums[i]) {
                barrNums[i] = true;
                narrNums[nIndex] = i;
                getCases(nIndex + 1);
                barrNums[i] = false;
            }
        }
    }

    // 두 시가지로 나눈 노드들이 연결되어 있는감
    private static boolean IsConnected() {
        boolean bResult = true;

        // 조합에서 꺼낸 경우의 수 부터
        // 연결 되어 있는지 봅시다
        barrDFS = new boolean[N];
        DFS(narrNums[0], narrNums);
        if (!bCheck(narrNums))
            return false;

        // 조합에 선택되지 않은 노드들이
        // 연결 되어 있는지 봅시다
        DFS(narrAnothers[0], narrAnothers);    
        if (!bCheck(narrAnothers))
            return false;

        // 여기까지 왔으면 반으로 나눈게 연결되어 있따.
        return true;
    }

    // 조합에서 꺼낸 노드들을 탐색하자
    private static void DFS(int nNode, int[] narrTemp) {
    	barrDFS[nNode] = true;
    	
    	for(int i = 0; i < narrTemp.length; i++) {
    		if(listNode.get(nNode).contains(narrTemp[i]) && !barrDFS[narrTemp[i]]) 
    			DFS(narrTemp[i], narrTemp);
    	}
    		
    }
    
    // 해당 배열들이 탐색이 되었는지 확인
    private static boolean bCheck(int[] narrTemp) {
        for(int i = 0; i < narrTemp.length; i++) {
            if(!barrDFS[narrTemp[i]])
                return false;
        }
        
        return true;
    }

}

Good Luck! (피드백 감사합니다!)