문제 이름부터 대놓고 '버블 소트'이고, 목표는 swap 횟수를 구하는 것입니다. 하지만 데이터 개수 이 500,000이라는 점에 주목해야 합니다.
지난 1377번 문제에서는 "정렬 전후의 인덱스 차이"만으로 답을 구했습니다. 하지만 그건 '패스(Pass) 횟수'를 묻는 문제였기 때문입니다. 이번처럼 '전체 Swap 횟수'를 물을 때는 최종 위치만 봐서는 그 과정에서 일어난 수많은 '새치기'의 총합을 알 수 없습니다.
여기서 병합 정렬(Merge Sort)이 등판합니다.
병합 정렬은 배열을 반으로 나누고 합치는 과정에서 정렬을 수행합니다. 이때 '오른쪽 그룹의 원소가 왼쪽 그룹보다 먼저 선택될 때'가 바로 역전(Swap)이 발생하는 순간입니다.
A[i] > A[j]라면, 오른쪽 원소 A[j]는 현재 남아있는 왼쪽 그룹의 모든 원소를 한꺼번에 뛰어넘어 앞으로 이동해야 합니다.j - k (원래 위치 j와 현재 들어갈 위치 k의 차이)가 바로 그 원소가 수행한 swap 횟수가 됩니다.import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
static final BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer st;
static int[] temp;
public static void main(String[] args) throws Exception {
int N = nextInt();
temp = new int[N];
int[] A = new int[N];
for (int i = 0; i < N; i++) {
A[i] = nextInt();
}
// 전체 swap 횟수는 int 범위를 초과하므로 long 출력
System.out.println(mergeSort(A, 0, N - 1));
}
/**
* 분할 정복을 통한 병합 정렬 및 Inversion Counting
*/
static long mergeSort(int[] A, int s, int e) {
if (s >= e) return 0;
int m = (s + e) >>> 1;
// 왼쪽 구간 swap + 오른쪽 구간 swap + 현재 병합 시 발생하는 swap
return mergeSort(A, s, m) + mergeSort(A, m + 1, e) + merge(A, s, m, e);
}
/**
* 두 구간을 병합하며 역전쌍(Swap) 계산
*/
static long merge(int[] A, int s, int m, int e) {
int i = s; // 왼쪽 구간 포인터
int j = m + 1; // 오른쪽 구간 포인터
int k = s; // temp 배열 포인터
long swapCount = 0;
while (i <= m && j <= e) {
if (A[i] <= A[j]) {
temp[k++] = A[i++];
} else {
/* * 핵심 로직: 오른쪽 원소가 새치기할 때
* 현재 위치(j)에서 들어가야 할 위치(k)를 뺀 값이
* 해당 원소가 뛰어넘은(swap한) 원소의 개수입니다.
*/
swapCount += (long) (j - k);
temp[k++] = A[j++];
}
}
// 남은 왼쪽 구간 원소 처리
while (i <= m) {
temp[k++] = A[i++];
}
// 남은 오른쪽 구간 원소 처리
while (j <= e) {
temp[k++] = A[j++];
}
// 정렬된 temp 배열을 원본 배열 A에 복사
for (k = s; k <= e; k++) {
A[k] = temp[k];
}
return swapCount;
}
/**
* Fast I/O를 위한 정수 입력 메소드
*/
static int nextInt() throws Exception {
while (st == null || !st.hasMoreTokens()) {
st = new StringTokenizer(br.readLine());
}
return Integer.parseInt(st.nextToken());
}
}
int 범위를 가볍게 넘어갑니다. 결과값 산출 시 반드시 long을 사용해야 합니다.