본문 바로가기

알고리즘 문제풀이

SCPC 2019 2차예선 풀이, 후기

SCPC 2차예선이 끝났다. 확실히 1차예선보다는 체감 난이도가 높았다.

 

1번 같은 경우에는 아이디어는 맞게 잡았는데 실수를 좀 했다. 점화식을 잘못 쓰거나 변수를 선언하고 초기화를 안 한다든지...하지만 제출횟수가 10번이라 다행이다!!ㅋㅋㅋ

 

4번은 휴리스틱이라 많이 어렵게 느껴졌는데 어떻게든 되지 않을까? 하는 심정으로 막 제출하다가 계속 안 돼서 각성하고 로컬에서 데이터 열심히 만들어서 확인해보고 만점을 받았다. 뭔가 데이터를 랜덤하게도 만들어 보고 절반만 빽빽하게 채워진 것도 만들어 보고 하면서 감을 잡았던 것 같다. 솔직히 이게 되려나... 하면서 제출했는데 만점이 뜬 문제ㅎㅎ

 

5번은 세그먼트 트리를 \(O(\lg{N})\)으로 구현해야 되는데 너무 오랜만이라 잘못 구현해서 \(O(N)\)이 되는 바람에 시간 초과를 한 번 받았다.

문제 1. 소수 수열 (100점)

설명

자릿수에 0을 포함하지 않는 양의 정수 \(n\)에 대해, 함수 \(F(n)\)을 다음과 같이 정의하자.

$$F(n) = \begin{cases} 1 + \text{max}_{m \in S_n} {F(m)}, & n\text{ is prime} \\ 0, & n\text{ is not a prime} \end{cases} $$

이때 집합 \(S_n\)은 \(n\)에서 자릿수를 하나씩 제거한 수들의 집합이다. 이를테면, \(S_{127} = \lbrace12, 17, 27\rbrace\)이다. 한 자리 양의 정수에 대해서는 공집합으로 정의한다. 이때 1은 소수가 아님에 유의하라.

 

이제 자릿수에 0을 포함하지 않는 두 양의 정수 \(A\), \(B\)가 주어졌을 때,

  • \(F(A) > F(B)\)이면 1을,
  • \(F(A) < F(B)\)이면 2를,
  • \(F(A) = F(B)\)이면 3을 출력하는 프로그램을 작성하시오.

제한조건: \(1 \le A, B \le 30,000\)

제한시간: 20개 이하의 테스트 케이스에 대한 실행시간 총합이 1초 이내 (Java 2초 이내).

풀이

주어진 점화식을 이용해서 직접 계산을 해 보면 알 수 있다.

이를테면, \(F(127) = 1 + \text{max}\left(F(12), F(17), F(27)\right) = 1 + \text{max}\left(0, 1 + \text{max}\left(F(1), F(7)\right), 0\right) = 3\)이 된다.

 

잘 알려진 \(O(N\sqrt{N})\) 알고리즘을 이용해 30,000까지 소수 여부를 저장하는 bool 테이블을 만든 다음, 점화식을 이용해 \(n\)이 증가하는 순서대로 다이나믹 프로그래밍을 적용하면 된다.

 

\(n\)이 작을 때 Base Case는 \(F(1) = 0\), \(F(2) = 1\), \(F(3) = 1\), \(F(4) = 0\), \(F(5) = 1\), \(F(6) = 0\), \(F(7) = 1\), \(F(8) = 0\), \(F(9) = 0\)이다. \(n \ge 10\)일 때는 점화식으로 해결된다.

 

다이나믹 프로그래밍의 시간복잡도는 \(O(N \log_{10}{N})\)이다. \(N \le 30,000\)이므로 제한시간에 걸릴 일은 거의 없다.

문제 2. 유사도 (150점)

설명

길이 \(n \)인 두 수열 \(a = a_1 a_2 \cdots a_n\)과 \(b = b_1 b_2 \cdots b_n\)이 주어진다. 두 수열의 유사도를 다음과 같이 정의하자.

$$F(a, b) = \sum_{i=1}^{n} 1\lbrace a_i = b_i \rbrace$$

이를테면 \(F(5 \,\, 2 \,\, 3 \,\, 7 \,\, 6 \,\, 1, \,\, 5 \,\, 7 \,\, 1 \,\, 2 \,\, 6 \,\, 3) = 2\)이다.

 

그런데 수열 \(b\)의 연속된 구간을 하나 택해 뒤집으면 유사도가 증가할 수 있다. 가령, \(b\)의 2번째 수부터 4번째 수까지를 뒤집으면 \(F(5 \,\, 2 \,\, 3 \,\, 7 \,\, 6 \,\, 1, 5 \,\, 2 \,\, 1 \,\, 7 \,\, 6 \,\, 3) = 4\)로, 이전보다 값이 증가한다.

 

따라서 다음 식의 최댓값을 구하는 프로그램을 작성하여라.

$$G(a, b) = \max_{1 \le i \le j \le n}{F(a_1 a_2 \cdots a_n,\,\, b_1 b_2 \cdots b_{i-1} \,\, b_j b_{j-1} \cdots b_{i+1} b_i \,\, b_{j+1} \cdots b_n)}$$

 

제한조건: \(n \le 5,000\)

제한시간: 40개 이하의 테스트 케이스에 대한 실행시간 총합이 2초 이내 (Java 4초 이내).

풀이

이 문제에서는 두 가지를 계산해야 한다. 하나는 임의 구간에서 수열 유사도이고, 다른 하나는 임의 구간에서 \(b\)를 뒤집었을 때 수열 유사도이다.

 

\(n \le 5,000\)이므로 다이나믹 프로그래밍을 이용한 \(O(n^2\)) 솔루션을 생각해보자.

 

1) 뒤집지 않을 때 수열 유사도

이 경우는 Prefix Sum을 이용해 해결할 수 있다. \(1..i\)까지의 유사도를 \(\text{DP}\lbrack i \rbrack \)라고 하면,

 

  • \(\text{DP}\lbrack 0 \rbrack = 0\)
  • \(\text{DP}\lbrack i \rbrack = \text{DP}\lbrack i-1 \rbrack + 1\lbrace a_i = b_i \rbrace \)

따라서 이 부분의 전체 시간복잡도는 \(O(n)\)이다. \(i..j\)의 유사도를 구하기 위해서는 다음 공식

$$ \text{DP}\lbrack i, j \rbrack = \text{DP} \lbrack j \rbrack - \text{DP} \lbrack i-1 \rbrack $$

을 활용하면 된다.

 

2) 뒤집을 때 수열 유사도

이 경우는 시작점이 모두 다르기 때문에 Prefix Sum을 사용하기는 어려운 것 같다. \(b\)를 뒤집을 때 \(i..j\)의 유사도를 \(\text{DPReverse}\lbrack i,j \rbrack \)라고 하면, 팰린드롬 문제와 유사하게 구간의 중심을 기준으로 이전 계산결과를 활용하여 점화식을 세울 수 있다.

 

  • \(\text{DPReverse}\lbrack i,i \rbrack = 1\lbrace a_i = b_i \rbrace \)
  • \(\text{DPReverse}\lbrack i,i+1 \rbrack = 1\lbrace a_i = b_{i+1} \rbrace + 1\lbrace a_{i+1} = b_i \rbrace \)
  • \(\text{DPReverse}\lbrack i,j \rbrack = \text{DPReverse}\lbrack i+1, j-1 \rbrack + 1\lbrace a_i = b_j\rbrace + 1\lbrace a_j = b_i \rbrace \)

따라서 이 부분의 전체 시간복잡도는 \(O(n^2)\)이다.

 

이제, 위에서 계산한 두 동적표 \(\text{DP}\lbrack i \rbrack \) 및 \(\text{DPReverse} \lbrack i,j \rbrack \)를 활용하면 구간 \(i..j\)를 뒤집는 경우의 유사도를 다음과 같이 계산할 수 있다.

$$ \text{DP}\lbrack i-1 \rbrack + \text{DPReverse} \lbrack i,j \rbrack + (\text{DP} \lbrack n \rbrack - \text{DP} \lbrack j \rbrack). $$

따라서 문제해결의 전체 시간복잡도는 \(O(n^2)\)가 되므로 시간 내에 해결 가능하다. 다만, \(\text{DP}\lbrack i \rbrack\)를 \(O(n)\)으로 하지 않고 \(\text{DPReverse}\lbrack i,j \rbrack \)처럼 \(O(n^2)\)로 했을 때는 시간초과가 났던 걸로 봐서 테스트 케이스가 조금 빡빡한 것 같다.

문제 3. 드론 탐험 (200점)

설명

아래 그림과 같은 동굴이 주어진다. 동굴의 모든 경계는 수직 혹은 수평 선분으로 구성되며, 위쪽 경계(천장)와 아래쪽 경계(바닥)은 한 점에서도 만나지 않는다.

 

동굴의 시작 \(x\)좌표는 0이고, 끝 \(x\)좌표는 \(L\)이다. 동굴의 시작과 끝 중 어떤 지점 \(S\)에서 시작하여 \(E\)에서 끝나는 택시경로(수직선과 수평선만 이용하는 경로) 중 최단거리인 것을 생각해보자. 그런데 \(S\)와 \(E\)를 잇는 최단거리 택시경로는 유일하지 않을 수 있다.

 

하나 이상의 최단거리 택시경로에 속하는 점을 모두 표시하면 선과 영역이 나타날 것이다. 해당 영역의 넓이를 구하는 프로그램을 작성하여라.

 

위쪽 경계는 \(A\)개의 가로 선분과 그들을 잇는 세로 선분으로 구성되며, 아래쪽 경계는 \(B\)개의 가로 선분과 그들을 잇는 세로 선분으로 구성된다. 이를테면, 아래 그림의 입력은 \(A = 10\), \(B = 9\)로 주어질 수 있다.

제한조건: \(1 \le A, B \le 100,000\), 모든 좌표는 절댓값이 \(10^9\) 이내.

제한시간: 30개 이하의 테스트 케이스에 대한 실행시간 총합이 1초 이내 (Java 2초 이내).

풀이

이 문제는 아이디어를 캐치하면 쉽게 풀린다. (물론 캐치하는 건 언제나 힘들어...)

 

예시 그림에서, S점에서 출발하여 \(y\)좌표 변화를 최대한 미루는 최적 경로를 생각해보자. \(y\)좌표 변화를 최대한 미루는 경로는 유일하고, 다음 그림과 같이 무언가 우리가 구하는 영역의 경계를 지나는 것처럼 생겼다. 어? 그러면 나머지 경계는 무엇으로 주어질 것인가. 당연히 대칭성에 착안하여 E점에서 S점으로 오면서 경로를 그려봐야 하지 않을까?

무언가 보인다?! 두 경로는 정확히 우리가 원하는 면적을 전부 감싸고 있다.

따라서 (S에서 출발해 \(y\)좌표 변화를 최대한 미루며 E에 도달하는 경로)와 (E에서 출발해 \(y\)좌표 변화를 최대한 미루며 S에 도달하는 경로)를 계산한 다음, 각 직사각형 구간에서 두 경로 사이에 있는 면적을 구해 모두 더해주면 된다. 시간복잡도는 \(O(A + B)\)로 선형적이다.

 

* 위쪽 경계와 아래쪽 경계를 처음 입력으로 받게 되면, 같은 \(x\)좌표 구간으로 분할되어 있지 않다. 따라서 mergesort를 하듯이 하나씩 보면서 통일해주어야 한다. 그러면 직사각형을 이어붙인 형태가 되며, merge하는 각 스텝마다 위쪽 경계 혹은 아래쪽 경계 중 하나 이상을 처리하기 때문에 이렇게 해서 만들어지는 \(x\)좌표 구간, 즉 직사각형의 개수는 최대 \(A + B\)개 이하가 된다. (참고로, 이 방법은 1차예선 5번 풀이(https://paido.tistory.com/15)에서 각 세그먼트를 합치는 데 사용한 방법과 거의 비슷하다.)

문제 4. 폭격 (250점)

설명

\(M \times N\) 격자의 각 칸 중 일부에 공장이 설치되어 있다. 격자의 \(3 \times 3\) 위치를 골라 폭격하면 해당 \(3 \times 3\) 위치의 공장은 전부 파괴된다.

 

최소한의 \(3 \times 3\) 폭격으로 격자의 공장을 전부 파괴하고 싶다. 당연하게도, \((M-2)/3 \times (N-2)/3\) 정도 개수의 위치 전부에 폭격하면 격자를 전부 덮어 공장을 전부 파괴할 수 있겠지만, 이것이 정말 최적인가? 당연히 아니다. 아래 경우를 생각해보면, 2회의 폭격만으로도 모든 공장을 파괴할 수 있음을 알 수 있다.

2016년 본선 문제 중에도 '폭격'이 있었다. 그때는 DP문제였다.

주어진 격자 크기와 공장 위치에 대해, 가능한 한 최소의 폭격 횟수를 계산하고 그때의 폭격 위치들을 출력하는 프로그램을 작성하여라. 이때 \(3 \times 3\) 폭격영역의 왼쪽 위의 좌표를 1-based로 출력한다. (격자의 모든 칸에 공장이 있을 수도 있을 것이다. 공장 개수에 대한 제한은 따로 없었다.)

 

폭격 위치는 서로 겹칠 수 있으나, 폭격되지 않고 남아있는 공장이 있으면 0점을 받는다. 각 테스트 케이스에 대해 주최측이 갖고 있는 최소 폭격 횟수를 \(A\)라고 하면, 작성한 프로그램은 폭격 횟수를 적어도 \(1.2A\)까지는 줄여야 해당 테스트 케이스에 대한 점수를 받을 수 있다.

 

제한조건: \(3 \le M \le 50\), \(3 \le N \le 500\)

제한시간: 30개 이하의 테스트 케이스에 대한 실행시간 총합이 1초 이내 (Java 2초 이내).

풀이

요즘 SCPC에 계속 근사 문제가 나오고 있다. 이런 식의 근사 최적화 문제는 항상 두 가지 단계로 풀이한다.

  1. 휴리스틱을 이용해 적당한 근사해 구하기.
  2. 무작위 탐색 알고리즘을 이용해 휴리스틱의 부족분을 보완하기 ("바닥에 남은 단물까지 빨아먹기").

1) 휴리스틱을 이용해 적당한 근사해 구하기

솔직히 이번 1차예선보다는 좀 복잡하게 느껴져서, 일단 아무거나 해 봤다. 다음과 같이 하면 82점이 나온다.

  • 폭격이 가능한 \((M-2) \times (N-2)\)가지 위치에 대해, 해당 위치로의 폭격으로 파괴할 수 있는 공장의 수를 계산한다. 최소한 하나의 공장이라도 파괴하는 모든 폭격 위치를 모은다.
  • 파괴할 수 있는 공장 수를 기준으로 내림차순 정렬한다.
  • 이제 폭격을 하나씩 보면서 해당 폭격이 필요한 경우 추가한다. 여기서 \(i\)번째 폭격이 "필요하다"는 말은 \(1..i-1\)번째 폭격을 모두 하더라도 \(i\)번째 폭격의 타겟 공장 중 커버되지 않는 것이 있다는 의미다.

1차예선 4번 문제는 휴리스틱만으로도 186/200점이 나왔지만, 이번에는 82/250점밖에 나오지 않았다. 따라서 그때보다 더 효과적인 탐색 알고리즘이 필요하다고 생각했다.

 

2) 무작위 탐색 알고리즘을 이용해 휴리스틱의 부족분을 보완하기

1차예선 때는 임의의 두 원을 뽑아 바꾸는 Local Search를 적용했지만, 이번에는 뭔가 더 강력한 알고리즘이 필요할 것 같았다. 왜냐면 1차예선 4번 문제의 경우 적당히 뽑아서 바꿔볼 수 있었던 반면에 이 문제의 경우 최적해를 찾기 위해서는 현재로써는 불필요한 폭격을 추가했다가 제거해야 할 수도 있기 때문이다. 즉 Local Minima를 넘기 위한 방법이 필요할 것 같았다. Local Minima 극복을 위해 가장 잘 알려진 알고리즘은 Simulated Annealing이다.

 

Simulated Annealing이란, 물리학에서 고체의 결정구조를 얻기 위해 사용하는 Annealing에서 모티브를 얻은 알고리즘이다. 고체의 결정구조가 잘 만들어졌다는 것은 결정구조의 에너지가 최소임을 의미한다. 이러한 결정구조를 얻기 위해서는 고체를 천천히 식혀야 한다. 급하게 식히면 결정구조에 결함(defect)이 발생하게 되며, 이는 에너지가 최소화되지 못한 상태에 해당한다.

 

이런 데서 영감을 얻어서, Simulated Annealing은 다음과 같은 방식으로 진행한다.

 

  • 현재 솔루션 \(P\)를 변형해 새로운 솔루션 후보 \(P^\prime \)을 찾는다.
  • \(P^\prime \)이 \(P\)보다 나으면 무조건 채택한다.
  • 하지만 \(P^\prime \)이 \(P\)보다 못한 경우에도 Local Minima를 넘기 위해서는 임시로 채택해야 하는 경우가 있을 수 있다. 따라서, 통계물리학의 아이디어를 빌려와 Boltzmann Factor \(\exp{\left(-\Delta E / {kT} \right) }\)의 확률로 \(P^\prime\)을 채택한다.
    • 여기서 \(\Delta E = E(P^\prime) - E(P)\)는 현재 솔루션 대비 새로운 솔루션의 에너지 차이를 나타낸다. 지금 상황에서 새로운 솔루션 \(P^\prime\)은 \(P\)보다 못하므로 \(\Delta E > 0\)이고, Boltzmann Factor는 1보다 작다.
    • Boltzmann constant \(k\)는 통계물리학적으로도 상수 이상의 의미가 없다. 그냥 1로 놓아도 무방하다.
    • 에너지 차이 \(\Delta E\)가 일정할 때, 온도 \(T\)가 높으면 \(P^\prime\)을 채택할 확률이 높지만 \(T\)가 낮으면 \(P^\prime\)을 채택할 확률이 낮다. 이는 온도에 대한 우리 직관에 부합한다.
  • 솔루션이 수렴하도록 하기 위해 온도 \(T\)는 높은 값에서 시작해 점점 낮추어 절대 영도에 가깝게 가져가야 한다. 절대 영도에서는 에너지 차이가 조금이라도 있으면 무조건 에너지가 낮은 상태가 점유된다. (* 절대 영도를 만든 후, 약간의 후처리가 필요하다. 아래에 설명)

이제 남은 과제는 솔루션의 에너지 함수 \(E(P)\)를 정의하는 것과, 새로운 솔루션 후보 \(P^\prime\)을 찾는 것이다.

우리가 최소화하고 싶은 변수는 폭격 위치의 개수이므로, \(E(P) = (\)폭격 위치의 개수\()\)로 정의하면 일단 기본은 할 것이다. 새로운 솔루션은 어떻게 찾아야 할까? 가장 쉽게 생각할 수 있는 건 다음 두 가지다.

 

  • 새로운 폭격 위치 추가
  • 기존 폭격 위치 중 불필요한 것 제거

따라서, 1/2의 확률로 추가 혹은 제거를 하도록 하였다. 반복횟수는 실험적으로 측정하여 50번 정도로 하였다. 온도는 (1/1 - 1/51)*50, (1/2 - 1/51)*50, ..., (1/50 - 1/51)*50 순으로 낮추었다. 그런데 이때 위에서 말한 후처리가 필요하다. 왜냐면 랜덤하게 추가된 폭격 위치 중 불필요한 것이 남아있을 수 있기 때문이다. 따라서 Annealing이 끝난 후 Local Search를 통해 불필요한 폭격 위치를 모두 제거해야 한다.

 

하지만 이것도 충분하지는 않다. Simulated Annealing의 특성상 무작위 알고리즘이기 때문에 매번 결과가 다를 수 있기 때문이다. 이럴 때는 반드시 전체 과정을 여러 번 반복하여 가장 좋은 해를 채택해야 한다. 10번 정도 반복하였더니 만점이 나왔다.

 

* 소스코드: https://paido.tistory.com/17

* 물론 더 간단한 풀이가 충분할 수도 있습니다. 간단하게 푸신 분은 알려주시면 감사하겠습니다.

문제 5. 존의 정사각형 (300점)

설명

\(M \times M\) 격자 내부의 정수점 중 일부에 해당하는 서로 다른 \(N\)개의 점이 주어진다. 각 점 \(P\)에 대해, \(P\)를 왼쪽 아래 꼭짓점으로 하는 정사각형 중 가장 큰 것을 그리고 싶다. 이때 다른 점을 침범할 수 없기 때문에 정사각형의 크기가 제한된다. 다만 다른 점이 경계에 오는 것은 가능하다. 아래 그림은 한 가지 예시를 나타낸다.

 

M=6, N=3인 경우. 이때의 해는 4+4+1=9가 된다.

격자 크기와 내부 정수점 \(N\)개가 주어졌을 때, 각 점에서 그릴 수 있는 정사각형 한 변 길이의 최댓값의 합을 출력하시오.

 

제한조건: \(2 \le M \le 10^9\), \(1 \le N \le 500,000\)

제한시간: 22개 이하의 테스트 케이스에 대한 실행시간 총합이 3초 이내 (Java 6초 이내).

풀이

어떤 점 \(P(a, b)\)에서 그릴 수 있는 정사각형 한 변 길이의 최댓값 \(F(P)\)는 다음 식으로 주어진다.

$$ F(P) = \min_{(x, y)\text{ s.t. } x > a\text{ and }y > b}{\max{(x - a, y - b)}}. $$

(여기서 최솟값은 모든 입력 점에 대해 취한다.)

 

따라서 최솟값 밑에 달려있는 제한조건에 대한 쿼리만 효율적으로 할 수 있으면 끝난다. 하지만 2D Range Query는 생각보다 구현하기가 깔깔하다. 어떻게 해야 할까? 대회 중에 생각해낸 방법은 \(x\)좌표가 제한조건이 되는 경우와 \(y\)좌표가 제한조건이 되는 경우를 나누어 생각하는 것이었다. 즉, 위 식을 다음과 같이 변형해보자.

$$ F(P) = \min{ \left( \min_{(x,y)\text{ s.t. } x > a\text{ and } y-x \ge b-a}{ (y - b) },\,\,\,\, \min_{(x,y)\text{ s.t. } y > b \text{ and } y-x \le b-a}{ (x - a) } \right) }. $$

 

이제 식이 두 개의 단일 \(\text{min}(\cdots)\) 항으로 나뉘었고, 두 식은 \(x, y\)를 바꾸면 서로 대칭적이다. 앞의 식만 계산하면 뒤의 식은 \(x, y\) 좌표를 바꿔준 다음 동일한 루틴으로 계산할 수 있다. 그러므로 앞의 식을 계산하는 방법에 대해 생각해보자.

 

우선, \(x > a\)라는 제한조건은 입력 점들을 \(x\)좌표에 대해 내림차순으로 정렬함으로써 해결할 수 있다. 지금까지 나왔던 점들에 대해서 최솟값을 취하면 되기 때문이다. 그러면 이제 \(y-x \ge b-a\)인 점 중 \(y\)좌표의 최솟값을 구해야 한다.

 

이를 위해, 각 점을 \((y-x, y)\)의 쌍으로 생각해보자.

앞의 것을 key, 뒤의 것을 value로 생각하면 key값이 일정 값 이상인 것들 중에서 value값의 최솟값을 구하는 문제가 된다.

이런 식의 쿼리 문제는 굉장히 자주 등장하는 것으로, Longest Increasing Subsequence (LIS)를 \(O(N \lg{N})\)에 구하는 알고리즘에도 사용된다. 참고로, \(O(N \lg{N})\) LIS는 작년 2차예선 4번 기출문제다.

 

어쨌든, 이를 해결하기 위해서는 key순으로 각 점을 정렬하여 key 순서의 인덱스를 부여한 다음, 세그먼트 트리를 이용해 Point Update, Range Query를 함으로써 한 번당 \(O(\lg{N})\)으로 전체 \(O(N \lg{N})\)에 문제를 해결할 수 있다. 값이 아직 들어오지 않은 엔트리의 경우 아주 큰 값으로 놓은 다음, 쿼리 값을 받아서 쓸 때 해당 값에 대해 예외처리를 해 주면 된다.

 

마지막으로, \(x\)좌표가 같은 점의 경우에는 반드시 \(y\)좌표가 작은 것부터 처리해야 한다. 왜냐면 \(P\)점과 \(x\)좌표가 같은 다른 점은 \(y\)좌표가 무엇이든지 간에 상관없이 \(P\)점에서 만들 수 있는 정사각형 크기에 제약이 되지 않기 때문이다. (왼쪽 경계에 존재하므로 상관이 없다.) 따라서 세그먼트 트리에 들어가지 않게 해야 한다. 반대도 문제가 될 수 있다는 생각이 들 수도 있지만, \(x\)좌표가 같고 \(y\)좌표가 작은 것들은 \(y-x \ge b-a\)라는 Range Query를 할 때 자동으로 제외되므로 문제가 없다. 이 정렬을 거꾸로 해서 한 번 틀렸다.

 

* 처음에 2D Range Query로 풀려고 Sparse Table 만들었다가 솔루션이 \(O (N \lg^3{N})\) 나오는 걸 보고 접었다. 다행이다. 그리고 \(x = a\) 혹은 \(y = b\)인 다른 점들은 전혀 상관이 없다는 걸 캐치하는 데 시간이 좀 걸렸다.

 

다른 방법으로 푸셨거나 질문이 있으시면 댓글 부탁드려요! 모두 수고하셨습니다.