

짝수 명의 사람들을 두 팀으로 나누려고 한다. 최대한 비슷한 전력으로 팀을 나누기 위해 아래와 같은 표를 사용한다.

팀의 전력은 개인의 능력의 합이 아닌 서로가 팀이 됐을 때의 능력치의 합으로 계산한다.
위의 표에서 i와 j는 각 선수를 의미하고 같은 팀이 됐을 때의 능력치를 의미한다
예를 들어, 1번과 2번이 같은 팀이 되는 경우 팀의 전체 능력치는 S(1,2) + S(2,1)이 된다.
예시는 전체 인원이 4명이기 때문에 2명씩 팀이 되지만 그 이상인 경우는 어떻게 계산할까?
가능한 순서쌍 조합을 모두 합하면 된다. 예를 들어, 6명을 3명으로 나누는 경우 (1, 3, 5), (2, 4, 6)으로 팀을 나눈다고 하자.
그렇다면 1팀의 능력치는 S(1, 3) + S(3, 1) + S(1, 5) + S(5, 1) + S(3, 5) + S(5, 3) 이 된다. 2팀도 마찬가지로 계산하면 된다.
위와 같은 방식으로 팀의 능력치의 차이가 최소일때의 값을 구하면 된다.
크게 두 과정으로 진행된다.
1번의 과정은 가능한 순서쌍을 뽑아야 한다. 기본적으로 주어진 자료만으로 그리디를 적용할 수는 없을 것 같다. 따라서, 완전탐색이 필요해 보인다.
뽑는 방식은 뽑힌 전력이 있는 선수를 visited 배열 정보에 저장하여 뽑게 한다. 노드의 방문 여부를 확인하는 것과 같다.
dfs를 사용해서 오름차순으로 팀을 구성하는 경우를 모두 탐색할 수 있다.
뽑을 때 주의해야 하는 것은 팀의 구성에는 순서가 없는 것이다. 가령, 3명의 팀을 뽑을 때
(1, 3, 5)와 (3, 1, 5)는 같다.
따라서, 이러한 중복을 피할 수 있는 코드를 작성해야 한다.
그렇다면 2번 능력치는 어떻게 비교하면 될까? 어렵지 않다.
1번에서 뽑은 팀의 정보는 visited 배열에 있을 것이다. 한 분기가 끝에 도달했을 때 visited 내의 true값인 인덱스는 1팀, false값인 인덱스는 2팀이 된다.
이를 바탕으로 팀의 능력치 합을 구하면 된다. 능력치를 구할 때는 2차원 배열을 통해서 팀 구성원의 인덱스를 조합하여 합산하면 된다.
package java_baekjoon;
import java.io.*;
import java.util.*;
public class prob14889 {
static int N;
static int[][] info;
static boolean[] visited;
static int ans = Integer.MAX_VALUE;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine());
visited = new boolean[N];
info = new int[N][N];
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 0; j < N; j++) {
info[i][j] = Integer.parseInt(st.nextToken());
}
}
dfs(0, -1);
System.out.println(ans);
}
static void dfs(int depth, int prev_select) {
if (depth == N / 2) {
ArrayList<Integer> selected_1 = new ArrayList<>();
ArrayList<Integer> selected_2 = new ArrayList<>();
for (int i = 0; i < N; i++) {
if (visited[i]) {
selected_1.add(i);
} else {
selected_2.add(i);
}
}
int ability_1 = Cal_Ability(selected_1);
int ability_2 = Cal_Ability(selected_2);
int dif = Math.abs(ability_1 - ability_2);
if (ans > dif) {
ans = dif;
}
return;
}
for (int i = prev_select + 1; i < N; i++) {
if (visited[i]) {
continue;
}
visited[i] = true;
dfs(depth + 1, i);
visited[i] = false;
}
}
static int Cal_Ability(ArrayList<Integer> team) {
int sum = 0;
for (int i = 0; i < team.size(); i++) {
for (int j = i + 1; j < team.size(); j++) {
sum += (info[team.get(i)][team.get(j)] + info[team.get(j)][team.get(i)]);
}
}
return sum;
}
}
Cal_Ability 함수는 능력치를 계산하는 함수이다. 파라미터로 ArrayList형을 받는다. 이는 분기가 끝냈을 때의 팀의 구성원을 원소로 갖는다.
팀을 뽑을 때의 반복문의 i는 prev_select로 초기화하는데 이는 이전에 뽑은 사람을 의미한다. 중복을 피하기 위해서 이전에 뽑은 사람 다음의 사람부터 뽑겠다는 의미이다.
이를 통해, 중복을 피하며 오름차순으로 팀을 구성할 수 있다.
물론, 중복을 포함해도 틀린 코드는 아니다. 하지만 팀의 인원이 N명이라면 한 조합에 중복되는 조합이 N! 존재하기 때문에 시간초과가 발생한다.
