이 문제는 단순한 완전 탐색으로 접근하면 시간 초과가 발생하기 쉬운 문제입니다. 과 의 크기를 고려하여 효율적인 탐색 전략을 수립해야 합니다.
입력 크기는 입니다.만약 의 모든 부 배열과 의 모든 부 배열을 일일이 구해서 비교한다면 어떻게 될까요?
문제를 해결하기 위해 다음 두 단계로 로직을 나눕니다.
sumA, sumB)에 저장합니다. 이 과정은 과 이 소요되지만, 일 때 충분히 수행 가능합니다.sumB)를 오름차순 정렬합니다.sumA를 순회하며, 값이 sumB에 몇 개 존재하는지 찾습니다.sumB에는 중복된 합의 값이 존재할 수 있습니다. 따라서 단순한 binarySearch가 아닌, 중복된 원소의 시작과 끝 위치를 찾는 Lower Bound(하한선)와 Upper Bound(상한선) 알고리즘을 사용해야 합니다.우리가 찾아야 하는 조건은 다음과 같습니다.
이를 변형하면 우리가 찾아야 할 타겟 값(Target)을 정의할 수 있습니다.
즉, sumA의 각 원소에 대해 sumB 내에서 값이 인 원소의 개수를 구하여 더하면 됩니다.
아래 코드는 입력된 로직을 그대로 유지하되, 가독성을 위해 포맷팅을 다듬었습니다.
import java.io.*;
import java.util.*;
class Main {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer st;
static int T, N, M;
static int[] A, B;
static long answer = 0;
public static void main(String[] args) throws IOException {
// 1. 입력 받기
T = Integer.parseInt(br.readLine());
N = Integer.parseInt(br.readLine());
A = new int[N];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < N; i++)
A[i] = Integer.parseInt(st.nextToken());
M = Integer.parseInt(br.readLine());
B = new int[M];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < M; i++)
B[i] = Integer.parseInt(st.nextToken());
// 2. 부 배열의 합 리스트 생성 (O(N^2))
List<Long> sumA = getSumList(A);
List<Long> sumB = getSumList(B);
// 3. 이분 탐색을 위한 정렬 (sumB만 정렬해도 됨)
Collections.sort(sumB);
// 4. sumA를 순회하며 sumB에서 Target 값 찾기
for (long a : sumA) {
long target = T - a;
// 중복된 값의 개수를 구하기 위해 Lower Bound와 Upper Bound 사용
int low = getLowBound(target, sumB);
int high = getUpperBound(target, sumB);
answer += high - low;
}
System.out.println(answer);
}
// Lower Bound: 찾고자 하는 값(target) 이상의 값이 처음 나타나는 위치
private static int getLowBound(long target, List<Long> list) {
int left = 0;
int right = list.size();
while (left < right) {
int mid = (left + right) / 2;
if (target <= list.get(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
// Upper Bound: 찾고자 하는 값(target)을 초과하는 값이 처음 나타나는 위치
private static int getUpperBound(long target, List<Long> list) {
int left = 0;
int right = list.size();
while (left < right) {
int mid = (left + right) / 2;
if (target < list.get(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
// 배열의 모든 부 배열 합을 구하는 메소드
private static List<Long> getSumList(int[] arr) {
List<Long> list = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
long sum = 0;
for (int j = i; j < arr.length; j++) {
sum += arr[j];
list.add(sum);
}
}
return list;
}
}
이 문제는 자료구조를 변환하여 시간 복잡도를 획기적으로 줄이는 대표적인 예제입니다.
특히 sumB 리스트의 크기가 최대 약 50만 개이므로, 이를 정렬하는 비용()과 탐색 비용()이 전체 성능을 좌우합니다.
문제 풀이 시 간과하기 쉬운 부분이 바로 데이터 타입입니다.
int 범위를 넘어설 수 있습니다. 코드에서 List<Long>을 사용하여 오버플로우를 방지했습니다.sumA의 크기 sumB의 크기까지 가능합니다.int 범위를 훨씬 초과합니다. 따라서 answer 변수는 반드시 long 타입을 사용해야 합니다.