임의의 두 수의 합을 확인하는 문제가 있었습니다. 수열을 정렬하고 이분 탐색을 적용하거나 두 포인터를 사용하면 해결 가능합니다.
임의의 세 수의 합을 확인하는 문제도 있었습니다.
가능한 순서쌍에 대해서 으로 모두 시도한 하면서 마찬가지로 이분 탐색으로 으로 찾는 문제입니다. 으로 한 용액을 선택하고 두 포인터로 나머지 두 용액을 찾을 수도 있습니다.
임의의 네 수의 합을 확인하는 문제도 있었습니다. 지금 보는 이 문제와 꽤 비슷해 보입니다. 서로 다른 네 수를 두 수로 만들기 위해서 와 에서 고를 수 있는 모든 경우의 수를 합치고, 와 도 마찬가지로 해 주는 식으로 배열을 두 개 만들면 각각의 배열에서 한 개씩 선택하는 것으로 만들 수 있습니다.
하지만 Parcel에서는 이런식의 풀이가 불가능합니다. 서로 다른 를 골라야 하기 때문입니다. 의 전처리한 원소 중에서 서로 다르게 개를 고르더라도 인덱스가 겹칠 수 있습니다.
우리는 부분 집합을 고를 수 있는지를 확인하는 것이 목표이기 때문에 주어진 수열을 정렬해도 상관 없습니다. 주어진 수열 를 정렬하고, 에서 서로 다르게 개를 고른 후에 작은 인덱스부터 라고 하겠습니다. 우리가 찾고자 하는 조건은 인 경우입니다. 개만 고르는 거면 두 포인터로 에 어떻게 할 수 있는데 개를 개로 줄이는 방법은 없을까요?
라고 합시다. 만 확인한다면 으로 만들어낼 수 있는 합 를 모두 확인할 수 있습니다. 그리고 는 오름차순이므로 범위에서 두 포인터를 통해 를 만족하는 가 있는지 찾으면 됩니다. 의 전처리를 위해서 배열 을 다음과 같이 정의합시다.
를 만족하는 순서쌍 중에서 의 인덱스의 최솟값을 저장. 만약 만족하는 순서쌍이 없다면 INF를 저장.
이렇게 하면 전처리 후에 에 저장되어 있는 값을 보고 까지 두 포인터로 를 찾으면 됩니다. 그런데 왜 최솟값을 저장해야할까요?
를 계산하는데 다음과 같은 상황이 둘 다 를 만족한다고 합시다.
1번
2번
1번에서 인 를 찾았으니 이제 범위에서 를 만족하는 를 찾아야합니다. 그런데 1번에서 를 찾을 수 있었다면 당연히 2번에서도 찾을 수 있습니다. 범위를 완전히 포함하면서 더 커졌으니까요. 오히려 1번에서 못찾았는데 2번에서 찾을 수 있는 경우가 있겠죠 따라서 는 계속 최솟값으로만 갱신해줘야 합니다.
public class Main {
// 정렬된 배열 S[head, tail]에 합이 k인 두 원소가 존재하는지 반환
static boolean f(int[] S, int head, int tail, int k) {
while (head < tail) {
int sum = S[head] + S[tail];
if (sum < k) head++;
else if (sum > k) tail--;
else return true;
}
return false;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
int W = Integer.parseInt(st.nextToken());
int N = Integer.parseInt(st.nextToken());
int[] S = new int[N];
st = new StringTokenizer(br.readLine(), " ");
for (int i = 0; i < N; i++) S[i] = Integer.parseInt(st.nextToken());
Arrays.sort(S);
// R[x] = S[i] + S[j] = x인 경우가 존재하는지
// 존재한다면 (i < j) 인 j인덱스를 저장, 중복된다면 가장 작은 값. 없다면 INF
int[] R = new int[400001];
Arrays.fill(R, N);
for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
int sum = S[i] + S[j];
R[sum] = Math.min(R[sum], j);
}
}
boolean flag = false;
for (int x = 1; x <= 400000; x++) {
if (f(S, R[x] + 1, N - 1, W - x)) {
flag = true;
break;
}
}
System.out.println(flag ? "YES" : "NO");
}
}
정렬에서 , 전처리에서 이고, 그 후에는 인데 실제로는 이보다 훨씬 빠르게 끝납니다