꾸물꾸물 졔의 개발공부

[백준] 17471 게리맨더링 - JAVA 본문

알고리즘/백준

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

체제 2023. 9. 5. 11:02

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

 

17471번: 게리맨더링

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

www.acmicpc.net

 

 

[풀이]

  • 조합 + BFS + 구현(시뮬레이션) 
  • 1. 두개의 선거구 각각에 포함될 구역을 정한다. - 조합 
  • 2. 각각의 선거구에 포함된 구역들이 서로 연결되어 있는지 확인한다. - BFS 
  • 3. 연결되어 있다면 인구수 차이, 그렇지 않다면 적당히 큰 값을 리턴한다. 

 

[구현 과정]

  • int num[] : 각 구역의 인구 수 
  • List<ArrayList<Integer>> graph : 각 구역의 연결정보 저장 
  • boolean select[] : 조합을 구현하는 과정에서 해당 구역이 선택되었는지 체크하는 배열 
  • min : 인구 수의 최솟값 (=최종값) 
    (모든 구역의 인구 총합의 최댓값이 1000이므로 1000으로 지정) 

 

1. N개의 구역의 각 연결정보를 입력받아 graph 에 저장한다. (인덱스는 편의상 0~N-1로 바꿔주었다.) 

 

2. 각각의 선거구에 포함될 구역을 찾기 위해 1~N/2개의 구역을 조합을 통해 고른다. 
(N/2개까지인 이유는 (2,4)로 나누는 것과 (4,2)로 나누는 것은 같기 때문이다.) 

💡 cnt개의 조합을 다 찾게 되면 select 배열에는 true인 구역 cnt개와, 나머지 false로 나누어진다. 

 

 

3. cnt 개의 조합을 구하였다면 두 구역의 인구 수 차이를 구한다. select 배열에서 true 값을 가진 구역과 false 값을 가진 구역을 각각 first, second 리스트에 넣는다. 

 

4. BFS 탐색을 통해 first / second 리스트 각각의 구역이 서로 연결되어 있는지 확인한다. 

 

5. 두 구역다 서로 연결 되어 있다면, 두 구역의 인구 총합 차이를 반환한다. 하나라도 연결되어 있지 않다면 1000을 반환한다. 

 

6. 모든 조합의 경우를 따져 최솟값을 갱신한 후, 최솟값이 아직도 1000이라면 -1 출력, 그렇지 않다면 min 값을 출력. 

 


 

[소스 코드]

import java.util.*;
import java.io.*;

public class Main {
	static int N;
	static int num[];
	static List<ArrayList<Integer>> graph = new ArrayList<>(); 
	static boolean select[]; //선택된 구역
	static int min = 1000; 
	public static void main(String[] args) throws Exception{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
		StringTokenizer st; 
		
		N = Integer.parseInt(br.readLine()); //구역의 갯수 
		num = new int[N];
		select= new boolean[N]; 
		for(int i=0; i<=N; i++) graph.add(new ArrayList<>()); 
		
		st= new StringTokenizer(br.readLine(), " "); 
		for(int i=0; i<N; i++) {
			num[i] = Integer.parseInt(st.nextToken());
		}
		
		for(int i=0; i<N; i++) {
			st= new StringTokenizer(br.readLine(), " "); 
			int n = Integer.parseInt(st.nextToken()); 
			while(n-- >0) {
				int next= Integer.parseInt(st.nextToken())-1; 
				graph.get(i).add(next);
			}
		}
		for(int i=1; i<=N/2; i++) {
			select(0, i); //N개 중에 i개 고르기 
		}
		
		if(min == 1000) System.out.println(-1);
		else System.out.println(min);
 	
	}//end of main
	
	private static void select(int index, int cnt) {
		//가능한지, 인구차이 몇인지 찾으러 ㄱㄱ 
		if(cnt==0) {
			min = Math.min(min, calSub(select)); 
			
			return; 
		}
		
		for(int i=index; i<N; i++) {
			select[i] = true; 
			select(i+1, cnt-1); 
			select[i] = false; 
		}
	}
	
	private static int calSub(boolean[] select) {
		List<Integer> first= new ArrayList<>(); 
		List<Integer> second= new ArrayList<>(); 
		int first_sum=0;
		int second_sum=0; 
		for(int i=0; i<N; i++) {
			if(select[i]) {
				first.add(i);
				first_sum += num[i]; 
			}
			else {
				second.add(i); 
				second_sum += num[i]; 
			}
		}
		
		//두 구역 다 서로 다 연결되어있어야 함 
		if(isConnected(first) && isConnected(second)) {
			return Math.abs(first_sum - second_sum); 
		}
		//하나라도 연결 안되어있으면 
		else return 1000;  
	}
    
	//리스트에 담겨있는 구역이 모두 연결되어있는지 확인하는 메소드 
	private static boolean isConnected(List<Integer> list) {
		Queue<Integer> que = new LinkedList<>();
		boolean visited[] = new boolean [N];
		visited[list.get(0)] = true;
		que.add(list.get(0)); 
		
		int count =1; //리스트에 있는 곳 다 갔는지 확인하기 위한 변수 
		while(!que.isEmpty()) {
			int node= que.poll();
			for(int next : graph.get(node)) {
				//같은 구역에 있는 곳이고 아직 방문안한 곳이면, 
				if(!visited[next] && list.contains(next)) {
					visited[next] = true; 
					que.add(next); 
					count ++; 
				}
			}
		}
		
		if(count != list.size()) return false;  
		return true; 
	}
}//end of class

'알고리즘 > 백준' 카테고리의 다른 글

[백준] 5052 전화번호 목록 - JAVA  (0) 2023.09.07
[백준] 12904 A와 B - JAVA  (0) 2023.09.06
[백준] 1068 트리 - JAVA  (0) 2023.09.04
[백준] 2294 동전 2 - JAVA  (0) 2023.09.01
[백준] 1806 부분합 - JAVA  (0) 2023.08.30