Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- C
- 연결 리스트
- 99클럽
- 플로이드-워셜
- 항해99
- Java
- 프로그래머스
- 코딩테스트준비
- 개발자취업
- 코딩테스트
- 개발자루틴
- 데이터베이스
- 코드트리
- 웹
- 알고리즘
- 운영체제
- 코딩테스트사이트추천
- 자바스크립트
- #코드트리 #코딩테스트 #코테공부 #백트래킹 #알고리즘 기초
- til
- 자바
- 오블완
- 프로그래밍 언어
- 파이썬
- 1일1코테
- 갭체크
- 티스토리챌린지
- 인접 행렬
- 코테공부
- 자료구조
Archives
- Today
- Total
와드의 블로그
15661번: 링크와 스타트 본문
문제
축구를 하기 위해 모인 N명을 스타트 팀과 링크 팀으로 나누려고 합니다. 두 팀의 인원수는 같지 않아도 되지만, 한 명 이상이어야 합니다. 사람마다 다른 사람과 같은 팀이 되었을 때 더해지는 능력치 S[i][j]가 주어질 때, 스타트 팀의 능력치와 링크 팀의 능력치 차이를 최소로 하는 프로그램을 작성하는 문제입니다.
- 입력: 첫째 줄에 N (4 ≤ N ≤ 20)이 주어집니다. 둘째 줄부터 N개의 줄에 능력치 배열 S가 주어집니다.
- 출력: 첫째 줄에 스타트 팀과 링크 팀의 능력치 차이의 최솟값을 출력합니다.
핵심 차이점: 인원수 제한(N / 2)의 해제
이전 문제인 '스타트와 링크'는 N명 중 정확히 N/2명을 뽑아야 했으므로 매개변수로 depth를 넘겨 조합(Combination)을 구했습니다. 하지만 이번 '링크와 스타트' 문제는 인원수 제한이 없으므로, N명의 사람 각각에 대해 "스타트 팀에 갈 것인가, 링크 팀에 갈 것인가" 하는 2가지 선택지만 부여하여 모든 부분 집합을 구하면 됩니다.
풀이 1: 부분 집합을 구하는 백트래킹 탐색
N명의 사람 각각을 true 팀과 false 팀으로 나누는 방식입니다. 모든 사람의 소속이 정해졌을 때(N명 모두 탐색 완료) 능력치 차이를 계산합니다.
- 상태 전이: 현재 탐색 중인 사람(idx)을 스타트 팀(visited[idx] = true)에 넣고 다음 사람을 탐색(go(idx + 1))합니다. 탐색을 마치고 돌아오면 링크 팀(visited[idx] = false)에 넣고 다음 사람을 탐색(go(idx + 1))합니다.
- 유효성 검사: 모든 사람을 배치했을 때(idx == n), 한 팀에 모든 사람이 몰려있으면(0명 vs N명) 안 됩니다. 이를 방지하기 위해 스타트 팀의 인원을 세고, 1명 이상 N-1명 이하일 때만 능력치를 계산하여 최솟값을 갱신합니다.
코드 1
import java.util.*;
import java.io.*;
public class Main {
static int n, ans = Integer.MAX_VALUE;
static int[][] s;
static boolean[] visited;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
n = Integer.parseInt(br.readLine());
s = new int[n][n];
for (int i = 0; i < n; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 0; j < n; j++) {
s[i][j] = Integer.parseInt(st.nextToken());
}
}
visited = new boolean[n];
// 0번 사람부터 팀 배정 시작
go(0);
System.out.println(ans);
}
static void go(int idx) {
// N명 모두 팀 배정이 끝난 경우
if (idx == n) {
int sum1 = 0;
int sum2 = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (visited[i] && visited[j]) {
sum1 += s[i][j] + s[j][i];
} else if (!visited[i] && !visited[j]) {
sum2 += s[i][j] + s[j][i];
}
}
}
// 최솟값 갱신
ans = Math.min(ans, Math.abs(sum1 - sum2));
return;
}
// 현재 사람을 스타트 팀(true)에 배정
visited[idx] = true;
go(idx + 1);
// 현재 사람을 링크 팀(false)에 배정
visited[idx] = false;
go(idx + 1);
}
}
풀이 2: 비트마스킹(Bitmasking)을 활용한 완벽한 최적화
비트마스킹을 활용하면, '팀에 적어도 1명 이상이 있어야 한다'는 예외 조건마저 반복문 범위 설정 하나로 완벽하게 해결할 수 있습니다. 메서드 분리를 통해 가독성을 더욱 높였습니다.
- 반복문 범위의 마법: 모든 상태를 나타내는 1 << n의 값 중, 절반인 (1 << n) / 2까지만 탐색합니다. (for (int i = 1; i < (1 << n) / 2; i++))
- i가 1부터 시작하므로 비트에 최소한 하나의 1이 포함됨이 보장됩니다. (스타트 팀 최소 1명 확보)
- i가 (1 << n) / 2 - 1 까지만 순회하므로, 가장 앞자리 비트는 무조건 0이 됩니다. (링크 팀 최소 1명 확보)
- A팀과 B팀이 완전히 뒤바뀌는 대칭 경우의 수도 탐색 절반 축소를 통해 제거되어 속도가 2배 빨라집니다.
- 메서드 분리 (getDiff): 비트 상태 x를 매개변수로 받아 두 팀의 능력치 차이를 반환하는 로직을 분리하여 메인 로직이 훨씬 깔끔해졌습니다.
- 이중 반복문 최적화: getDiff 내부에서 배열을 순회할 때, j = i + 1부터 순회하고 s[i][j] + s[j][i]를 한 번에 더해주면 연산량을 정확히 절반으로 줄일 수 있습니다.
코드 2
import java.util.*;
import java.io.*;
public class Main {
static int n, ans = Integer.MAX_VALUE;
static int[][] s;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
n = Integer.parseInt(br.readLine());
s = new int[n][n];
for (int i = 0; i < n; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for (int j = 0; j < n; j++) {
s[i][j] = Integer.parseInt(st.nextToken());
}
}
// 절반만 탐색해도 모든 팀 분배 조합을 확인할 수 있음
for (int i = 1; i < (1 << n) / 2; i++) {
ans = Math.min(ans, getDiff(i));
}
bw.write(ans + "");
bw.flush();
}
static int getDiff(int x) {
int sum1 = 0;
int sum2 = 0;
for (int i = 0; i < n; i++) {
// j를 i + 1부터 시작하여 연산량을 절반으로 최적화
for (int j = i + 1; j < n; j++) {
// 둘 다 1번 팀인 경우
if ((x & (1 << i)) != 0 && (x & (1 << j)) != 0) {
sum1 += s[i][j] + s[j][i];
}
// 둘 다 0번 팀인 경우
else if ((x & (1 << i)) == 0 && (x & (1 << j)) == 0) {
sum2 += s[i][j] + s[j][i];
}
}
}
return Math.abs(sum1 - sum2);
}
}
알고리즘 분류
브루트포스 알고리즘 (Brute Force), 백트래킹 (Backtracking), 비트마스킹 (Bitmasking)
'PS > B.O.J' 카테고리의 다른 글
| 10972번: 다음 순열 (0) | 2026.04.30 |
|---|---|
| 1248번: Guess (1) | 2026.04.30 |
| 14501번: 퇴사 (0) | 2026.04.29 |
| 1759번: 암호 만들기 (0) | 2026.04.29 |
| 18290번: NM과 K (1) (0) | 2026.04.28 |