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

 

17471번: 게리맨더링

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

www.acmicpc.net

코드설명

조합(Combination) + DFS(깊이우선탐색) + 유니온파인드(Union Find) + 구현 을 활용하는 문제입니다.

 

 

문제에 대한 큰 그림은,

모든 가능한 선거구 나누기를 DFS 및 조합으로 탐색하면서, Union-Find를 사용하여 선거구를 연결하고 최소 인구 차이를 찾아내는 방식으로 구현합니다.

 

이떄 선거구 나누는 로직은, 어떤 선거구를 먼저 뽑느냐는 전혀 상관이 없으므로 단순히 DFS로 RED인지, BLUE인지 나눠주며 완전탐색시켜줍니다.

 

또한 이 문제는 Union-find가 아닌 BFS로도 가능합니다. 먼저 똑같이 그룹을 나눠줍니다.

이후에 visited 배열로 해당 배열이 아직 연결된 흔적이 없다면, 모두 연결을 시도합니다. 이떄 이 연결은 물론, 같은 그룹내에서만 작동되어야만 합니다. 또한, 이 연결이 시작되었다는 의미는 선거구가 하나의 덩어리로 나눠졌다는 것을 의미합니다. 이떄 나눠지는 부분이 2개일경우에만 2개의 선거구로 모두 연결된 상태라는 의미입니다. 3개라면, 해당 선거구에 동떨어진 선거구가 존재한다는 의미입니다.

 

개인적으로는 Union-Find를 사용하기에 적합한 문제라고 생각이 듭니다.

또한, Node 하나의 객체로 그룹의 정보, 사람의 개수, 연결된 그래프를 모두 처리함을 통해서 효율적으로 코딩할 수 있습니다.

 

코드를 구현하며 실수 & 고민했던 점입니다.

1. 각 마을의 선거구 그룹(1, 2) 를 group 배열에 따로 저장할까 아니면 Node에 저장할까에 대한 고민.

2. 각 마을을 Node로 표현하는것이 맞을까에 대한 고민.

3. Node = new Node[N]을 한뒤 ArrayList에서 그룹을 만들듯이 Node 생성자로 다시 메모리 초기화 시켜야주어야한다는점.

4. 마지막에 fidnParent로 갱신된 그룹을 하나의 통일된 부모로 맞춰주어야한다는점. 그래야만, 올바르게 그룹의 개수를 셀 수 있습니다.

5. 모든 조합을 완성했다면, 새로운 parent[i]=i 를 통해 새로 부모배열을 초기화시켜주어야합니다.

 

코드의 주요 로직입니다.

  1. DFS (깊이 우선 탐색):
    • DFS 함수는 재귀적으로 호출되며, 현재 선거구의 번호를 가리키는 level을 인자로 받습니다.
    • level이 N + 1이 되면 모든 선거구에 대한 그룹을 결정했다는 의미이기에 인구수 차이를 계산하기 위한 로직을 실행합니다.
  2. Union-Find
    • 선거구를 그룹으로 나누기 위해 Union-Find 알고리즘을 사용합니다.
    • findParent: 해당 선거구가 속한 그룹의 부모를 찾고, 해당 그룹의 부모의 번호로 해당 선거구를 갱신합니다.
    • unionFind: 두 선거구를 하나의 그룹으로 합치는 메서드입니다.
  3. 연결된 선거구 체크:
    • DFS 함수 내에서 각 선거구가 연결된 선거구와의 그룹을 체크합니다.
    • 연결된 선거구가 같은 그룹에 속하면서 같은 Parent를 가지고있지 않다면, ( 즉 Union-Find가 되지 않았다면 ), 두 선거구를 하나의 그룹으로 합칩니다.
  4. 그룹에 속한 선거구의 부모 통일:
    • 최종적으로 그룹이 결정된 후에는 각 선거구의 부모를 통일시켜줍니다.
  5. 인구 차이 계산:
    • 각 선거구의 그룹을 통일시켜 연결 여부를 확인한 후, 두 그룹의 인구수 차이를 계산합니다.
    • 최종적으로 계산된 인구 차이가 현재까지의 최소 차이보다 작다면 업데이트합니다.

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.StringTokenizer;
 
public class Main {
	
	public static int N, M;
	public static int[] arr;
	public static int[] group;
	public static int answer = 100*100 + 1;
	public static Node[] nodeArr;
	public static int[] parent;
	public static void main(String[] args) throws IOException{
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	StringTokenizer st = new StringTokenizer(br.readLine());
    	
    	N = Integer.parseInt(st.nextToken());
    	arr = new int[N + 1];
    	nodeArr = new Node[N + 1];
    	parent = new int[N + 1];
    	
    	for(int i=1;i<=N;i++) {
    		parent[i] = i;
    	}
    	
    	for(int i=1;i<=N;i++) {
    		nodeArr[i] = new Node();
    	}
    	st = new StringTokenizer(br.readLine());
    	for(int i=1;i<=N;i++) {
    		arr[i] = Integer.parseInt(st.nextToken());
    		nodeArr[i].peopleCnt = arr[i];
    	}
    	
    	for(int i=1;i<=N;i++) {
    		st = new StringTokenizer(br.readLine());
    		int a = Integer.parseInt(st.nextToken()); //연결된 선거구의 개수
    		for(int j=1;j<=a;j++) {
    			int b = Integer.parseInt(st.nextToken()); //연결할 선거구.
    			nodeArr[i].connectedArr.add(b);	 //각 선거구를 연결합니다.
    			nodeArr[b].connectedArr.add(i);	 //각 선거구를 연결합니다.
    		}
    	}
    	
    	DFS(1);
//		System.out.println("===================");
//		for(int i=1;i<=N;i++) {
//			System.out.print(" "+nodeArr[i].group);
//		}
//		System.out.println();
    	
		if(answer == 100*100 + 1)
			System.out.println(-1);
		else 
			System.out.println(answer);
	}
	public static int findParent(int x) {
		if( x == parent[x]) return x;
		return parent[x] = findParent(parent[x]);
	}
	public static void unionFind(int a, int b) {
		a = findParent(a);
		b = findParent(b);
		if( a < b) parent[b] = a;
		else parent[a] = b;
	}
	
	public static void DFS(int level) {
		if(level == N+1) { //만약 N개 모두 바꾸었다면.
			for(int i=1;i<=N;i++) {
	    		parent[i] = i;
	    	}
			
//			System.out.println("===================");
//			for(int i=1;i<=N;i++) {
//				System.out.print(" "+nodeArr[i].group);
//			}
//			System.out.println();
			
			// 지금까지 구역을 두개의 선거구로 나누었습니다.
			int oneGroup = 0;
			int twoGroup = 0;
			
			//1. 각 구역은 두 선거구 중 하나에 포함되어야 합니다.
			//2. 선거구는 구역을 적어도 하나 포함해야하고, 한 선거구에 포함되어있는 구역은 모두 연결되어있어야합니다.
			//구역 A에서 인접한 구역을 통해서 구역 B로 갈 수 있을때, 두 구역은 연결되어잇다고 합니다.
			for(int i=1;i<=N;i++) {
				Node temp = nodeArr[i];
				for(int j=0; j<temp.connectedArr.size();j++) {
					Node next = nodeArr[temp.connectedArr.get(j)];
					//같은 그룹이면서 연결되어잇다면 UnionFind를 통해 하나의 그룹으로 집합으로 묶어줍니다.
					if(temp.group == next.group && findParent(i) != findParent(temp.connectedArr.get(j))) {
						unionFind(i, temp.connectedArr.get(j));
					}
				}
			}
			
			HashSet<Integer> hashset = new HashSet<Integer>();
			for(int i=1; i<=N;i++) {
//				System.out.print(" "+parent[i]);
				findParent(i); //마지막으로 findParent로 모든 Parent를 통일시켜줍니다.
				hashset.add(parent[i]);
			}
			if(hashset.size() >= 3) { //만약 3보다 크거나 같다면 연결되어있지 않은 선거구가 존재하므로 불가하다.
				return ; 
			}
			if(hashset.size() == 1) {
				return ;
			}
			
			for(int i=1; i<=N; i++) {
				if( nodeArr[i].group == 1) {
					oneGroup += nodeArr[i].peopleCnt;
				}else if(nodeArr[i].group == 2) {
					twoGroup += nodeArr[i].peopleCnt;
				}
			}
			int result = Math.abs(oneGroup - twoGroup);
			answer = Math.min(answer, result);
			
			return; 
		}
		
		nodeArr[level].group = 1;
		DFS(level + 1);
		nodeArr[level].group = -1;
		
		nodeArr[level].group = 2;
		DFS(level + 1);
		nodeArr[level].group = -1;
	}
	
}	

class Node{
	int peopleCnt = 0; //인구수
	int group = -1; //만약 1번 선거구라면 1, 2번 선거구라면 2 로 설정할 것 입니다.
	ArrayList<Integer> connectedArr = new ArrayList<Integer>();
	public Node(int peopleCnt) {
		this.peopleCnt = peopleCnt;
	}
	public Node() {
		
	}
		
}

 

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

public class Main {
	public static int N,M, K;
	public static int answer = Integer.MAX_VALUE;
	public static int[] arr;
	public static Node[] node;
	public static int RED = 1, BLUE = 2;
	public static int[] parent;
    public static void main(String[] args) throws IOException{
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    	StringTokenizer st = new StringTokenizer(br.readLine());

    	N = Integer.parseInt(st.nextToken());
    	arr = new int[N + 1];
    	node = new Node[N + 1];
    	parent = new int[N+1];
    	for(int i=0;i<=N;i++) {
    		parent[i] = i;
    		node[i] = new Node(0, -1);
    	}
    	st = new StringTokenizer(br.readLine());
    	for(int i=1;i<=N;i++) {
    		node[i].people = Integer.parseInt(st.nextToken());
    	}
    	
    	for(int i=1;i<=N;i++) {
    		st = new StringTokenizer(br.readLine());
    		
    		int t = Integer.parseInt(st.nextToken());
    		for(int j=0;j<t;j++) {
    			int connected = Integer.parseInt(st.nextToken());
    			node[i].connectedArr.add(connected);
    		}
    	}
    	simulate(1);
    	
    	if(answer == Integer.MAX_VALUE) {
    		System.out.println("-1");
    	}else {
    		System.out.println(answer);
    	}
    	
    }
    
    
    public static void simulate(int level) {
    	if(level == N+1) {
    		for(int i=0;i<=N;i++) {
        		parent[i] = i;
        	}
    		
    		for(int i=1; i<=N;i++) {
    			ArrayList<Integer> graph = node[i].connectedArr;
    			for(int j=0;j<graph.size();j++) {
    				if(node[i].group == node[graph.get(j)].group && findParent(i) != findParent(graph.get(j)) ) {
    					unionParent(i, graph.get(j));	
    				}
    			}
    		}
    		
    		for(int i=1; i<=N;i++) {
    			findParent(i);
    		}
    		
			//합친 후에 만약 findParent를 모두 했을떄 개수가 2개가 아닌, 3개이상이라면 해당 연결되지 않은 선거구가 존재하는 것 입니다.
			HashSet<Integer> hashset = new HashSet<Integer>();
			for(int i=1; i<=N;i++) {
				hashset.add(parent[i]);
			}
			int redCnt = 0;
			int blueCnt = 0;
			if(hashset.size() >= 3) {
				return ;
			}
			else if(hashset.size() == 1) {
				return ;
			}
			else if(hashset.size() == 2) {
				for(int i=1; i<=N;i++) {
					if(node[i].group == RED) {
						redCnt += node[i].people;
					}else {
						blueCnt += node[i].people;
					}
				}
				int diff = Math.abs(redCnt - blueCnt);
				answer = Math.min(answer, diff);
			}
			
    		return ;
    	}
    	
    	//순서는 중요하지 않다... 그렇다면, 단순히 group으로 나누자.
    	node[level].group = RED;
    	simulate(level + 1);
    	node[level].group = -1;
    	
    	node[level].group = BLUE;
    	simulate(level + 1);
    	node[level].group = -1;
    	
    }
    
    public static int findParent(int x) {
    	if( x == parent[x]) return x;
    	else return parent[x] = findParent(parent[x]);
    }
    
    public static void unionParent(int a, int b) {
    	a = findParent(a);
    	b = findParent(b);
    	
    	if( a < b) {
    		parent[b] = a;
    	}else {
    		parent[a] = b;
    	}
    }
    
    
}

class Node{
	int people;
	int group = -1; 
	ArrayList<Integer> connectedArr = new ArrayList<Integer>();
	public Node(int people, int group) {
		this.people = people;
		this.group = group;
	}
}

+ Recent posts