https://www.acmicpc.net/problem/17471
코드설명
조합(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 를 통해 새로 부모배열을 초기화시켜주어야합니다.
코드의 주요 로직입니다.
- DFS (깊이 우선 탐색):
- DFS 함수는 재귀적으로 호출되며, 현재 선거구의 번호를 가리키는 level을 인자로 받습니다.
- level이 N + 1이 되면 모든 선거구에 대한 그룹을 결정했다는 의미이기에 인구수 차이를 계산하기 위한 로직을 실행합니다.
- Union-Find
- 선거구를 그룹으로 나누기 위해 Union-Find 알고리즘을 사용합니다.
- findParent: 해당 선거구가 속한 그룹의 부모를 찾고, 해당 그룹의 부모의 번호로 해당 선거구를 갱신합니다.
- unionFind: 두 선거구를 하나의 그룹으로 합치는 메서드입니다.
- 연결된 선거구 체크:
- DFS 함수 내에서 각 선거구가 연결된 선거구와의 그룹을 체크합니다.
- 연결된 선거구가 같은 그룹에 속하면서 같은 Parent를 가지고있지 않다면, ( 즉 Union-Find가 되지 않았다면 ), 두 선거구를 하나의 그룹으로 합칩니다.
- 그룹에 속한 선거구의 부모 통일:
- 최종적으로 그룹이 결정된 후에는 각 선거구의 부모를 통일시켜줍니다.
- 인구 차이 계산:
- 각 선거구의 그룹을 통일시켜 연결 여부를 확인한 후, 두 그룹의 인구수 차이를 계산합니다.
- 최종적으로 계산된 인구 차이가 현재까지의 최소 차이보다 작다면 업데이트합니다.
코드
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;
}
}
'알고리즘 > Disjoint Set' 카테고리의 다른 글
[백준] 10451 순열 사이클 - 유니온 파인드(UnionFind, Disjoint Set) + DFS(깊이우선탐색) JAVA (0) | 2024.07.30 |
---|---|
[백준] 20040 사이클 게임 - 트리(Tree) + 분리집합(disjoint set) + 유니온파인드(Union Find) JAVA (0) | 2023.11.24 |
[백준] 10775 공항 - 유니온파인드(Union Find) + 그리디(greedy) JAVA (0) | 2023.09.06 |
[백준] 4195 친구 네트워크 - 유니온파인드(Union Find) + 해쉬맵(HashMap) JAVA (0) | 2023.09.05 |
[백준] 16562 친구비 - 유니온파인드(Union Find) + 최소조건(Minimum) JAVA (0) | 2023.09.05 |