본문 바로가기

알고리즘/BOJ

[JAVA] BOJ(백준) - 게리맨더링 - 17471

문제내용

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

 

17471번: 게리맨더링

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

www.acmicpc.net

백준시의 시장 최백준은 지난 몇 년간 게리맨더링을 통해서 자신의 당에게 유리하게 선거구를 획정했다. 견제할 권력이 없어진 최백준은 권력을 매우 부당하게 행사했고, 심지어는 시의 이름도 백준시로 변경했다. 이번 선거에서는 최대한 공평하게 선거구를 획정하려고 한다.

백준시는 N개의 구역으로 나누어져 있고, 구역은 1번부터 N번까지 번호가 매겨져 있다. 구역을 두 개의 선거구로 나눠야 하고, 각 구역은 두 선거구 중 하나에 포함되어야 한다. 선거구는 구역을 적어도 하나 포함해야 하고, 한 선거구에 포함되어 있는 구역은 모두 연결되어 있어야 한다. 구역 A에서 인접한 구역을 통해서 구역 B로 갈 수 있을 때, 두 구역은 연결되어 있다고 한다. 중간에 통하는 인접한 구역은 0개 이상이어야 하고, 모두 같은 선거구에 포함된 구역이어야 한다.

아래 그림은 6개의 구역이 있는 것이고, 인접한 구역은 선으로 연결되어 있다.

아래는 백준시를 두 선거구로 나눈 4가지 방법이며, 가능한 방법과 불가능한 방법에 대한 예시이다.

공평하게 선거구를 나누기 위해 두 선거구에 포함된 인구의 차이를 최소로 하려고 한다. 백준시의 정보가 주어졌을 때, 인구 차이의 최솟값을 구해보자.

입력

첫째 줄에 구역의 개수 N이 주어진다. 둘째 줄에 구역의 인구가 1번 구역부터 N번 구역까지 순서대로 주어진다. 인구는 공백으로 구분되어져 있다.

셋째 줄부터 N개의 줄에 각 구역과 인접한 구역의 정보가 주어진다. 각 정보의 첫 번째 정수는 그 구역과 인접한 구역의 수이고, 이후 인접한 구역의 번호가 주어진다. 모든 값은 정수로 구분되어져 있다.

구역 A가 구역 B와 인접하면 구역 B도 구역 A와 인접하다. 인접한 구역이 없을 수도 있다.

출력

첫째 줄에 백준시를 두 선거구로 나누었을 때, 두 선거구의 인구 차이의 최솟값을 출력한다. 두 선거구로 나눌 수 없는 경우에는 -1을 출력한다.

제한

  • 2 ≤ N ≤ 10
  • 1 ≤ 구역의 인구 수 ≤ 100

문제 풀이

일단 선거구를 0,1 두가지로 나누기 위해 조합을 이용했다. 어차피 범위가 10까지라서 충분할꺼라고 생각.

이후 선거구에서 연결된 지역 확인하는 과정은.. 처음엔 Union-Find를 쓰려고 했는데 잘안풀렸고, 비슷한 생각을 갖고 있는 다른 분의 풀이를 봤는데 그분은 BFS로 연결을 확인 했다. 그래서 다시 그분처럼 BFS로 연결된 지역 확인하도록 해봤다.

 

크게 과정은 총 3가지다.

1. 선거구 나누기. (모든 경우의 수, 조합)

2. 나눠진 선거구가 옮바른 선거구인가 확인하기(BFS 사용)

3. 옮바른 선거구 일때, 인구수 차이 계산 및 최솟값 갱신

 

이 과정을 거치면되고, 최대한 코드에 주석을 달아놨으니 보면서 이해하자.


코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main {
	static int n,min;
	static int[] people;
	static boolean[] check;
	static int[] arr; //인덱스:지역 , 요소값:선거구
	static ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();//간선표시
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		n = Integer.parseInt(br.readLine());
		min = Integer.MAX_VALUE;
		people = new int[n];
		arr = new int[n+1];
		
		
		StringTokenizer st = new StringTokenizer(br.readLine());
		for(int i=0; i<n; i++)
			people[i] = Integer.parseInt(st.nextToken());
		
		for(int i=0; i<=n; i++)
			list.add(new ArrayList<Integer>());
		
		for(int i=1; i<=n; i++) {
			st = new StringTokenizer(br.readLine());
			int cnt = Integer.parseInt(st.nextToken());
			for(int j=0; j<cnt; j++) 
				list.get(i).add(Integer.parseInt(st.nextToken()));
		}
		/////////////////////////////////////////////////////////////////// 입력
		
		//선거구 조합
		back(1);
		if(min==Integer.MAX_VALUE) System.out.println(-1);
		else System.out.println(min);
	}
	
	//선거구 조합, depth: 지역을 몇개 사용했는지 나타내줄 변수
	//1부터 시작하는 이유는 arr의 크기를 인덱스와 지역번호를 맞추기 위해 n+1로 했기때문.
	public static void back(int depth) {
		//모든 지역에 선거구가 정해졌을때
		if(depth==n+1) {
			check = new boolean[n+1];
			int a=0, b=0;//a:1번 선거구 총 인구수, b:0번 선거구 총 인구수
			for(int i=0; i<n; i++) {
				if(arr[i+1]==1) a+=people[i];
				else b+=people[i];
			}
			
			//cnt는 연결된 선거구를 나타냄.
			//즉, 선거구가 2개로 잘 분할됐는지 확인하기 위한 변수임.
			int cnt=0;
			//bfs돌리면서 지역들을 연결시킴.
			//예를들어, 문제에서 처럼 1:{1,2,3,4}, 0:{5,6} 이라면
			//i==1에서 1,2,3,4가 연결됨. 이후 i==5에선 6으로 연결된 간선이 없기 때문에
			//i==6일때 link함수가 한번더 실행됨. 그래서 cnt==3이 돼서 불가능한 선거구가 되는것임.
			for(int i=1; i<=n; i++) {
				if(check[i]) continue;
				link(i,arr[i]);//지역 연결 시키기(bfs)
				cnt++;
			}
			
			//선거구가 2개 나왔을때 인구수 차이 계산.
			if(cnt==2) min = Math.min(min, Math.abs(a-b));
			return;
		}
		
		//먼저 1번선거구 부터 지역 뽑음.
		arr[depth] = 1;
		back(depth+1);
		
		//1번 선거구를 depth지역까지 다뽑으면 하나씩 줄이면서 0번선거구 지역 뽑기.
		arr[depth] = 0;
		back(depth+1);
	}
	
	//num:지역, local:선거구(1or0)
	public static void link(int num, int local) {
		Queue<Integer> q = new LinkedList<Integer>();
		q.add(num);
		check[num] = true;
		
		while(!q.isEmpty()) {
			int cur = q.poll();
			
			//현재 지역과 연결된 다음 지역들 탐색
			for(int next:list.get(cur)) {
				//현재 지역의 선거구와 다음 지역의 선거구가 같을때 방문처리.
				if(arr[next]==local && !check[next]) {
					check[next]=true;
					q.add(next);
				}
			}
		}
	}
}

마치며

뭔가 아직도 선거구를 확인하는 부분은 Union-Find로도 가능할꺼 같은데,, 이부분을 고민해봐야 할듯 하다.