이전 포스트
solved.ac class 7++ 도전기 1
팀연습셋(NWERC 2020)과 2018년도 ICPC 업솔빙을 끝마쳤으니, 다시 class 7++ 등반으로 돌아왔습니다. 오늘은 class 7 문제중 5개를 골라(마찬가지로 풀이를 떠올린 문제들만 골랐습니다.), 100분 안에 해결하는 것을 목표로 했습니다. 5문제 중 두 문제 풀이가 거의 똑같고, 한 문제는 지난주에 푼 문제랑 거의 똑같아서 지난번보다 문제수가 하나 많음에도 시간을 줄였는데, 결과적으로는 썩 좋지 못한 판단이었던 것 같네요.. 아무튼 문제 목록은 다음과 같습니다.
A. BOJ 3033 - 가장 긴 문자열
B. BOJ 3878 - 점 분리
C. BOJ 11407 - 책 구매하기 3
D. BOJ 11479 - 서로 다른 부분 문자열의 개수 2
E. BOJ 18227 - 성대나라의 물탱크
A. BOJ 3033 - 가장 긴 문자열 (Solved at 18 min)
티어: 플래티넘 III
LCP 배열 자체가 suffix array가 주어졌을 때, 사전순으로 바로 뒤에 있는 suffix와 처음 몇 글자가 일치하는지를 나타내기 때문에, LCP 배열의 최댓값을 출력하기만 하면 되는 문제입니다.
코드
1 |
|
D. BOJ 11479 - 서로 다른 부분 문자열의 개수 2 (Solved at 23 min)
티어: 플래티넘 II
A번과 마찬가지로 Suffix Array를 사용하는 문제입니다. 우선 i번째 suffix는 문자열의 길이 - i개의 ‘i번째에서 시작하는’ 부분 문자열을 포함하고 있습니다. 이 중에서 다음 suffix와 겹치는(서로 같은) 부분 문자열은 lcp[i]개이므로, 각 suffix에 대해서, lcp[i]개만큼은 제외해주어야 합니다. 이 값을 전체 suffix에 대해서 계산해서 더해주면 이 문제를 해결할 수 있습니다. 다만 문자열 S의 길이가 최대 100만이기 때문에, 시간제한이 좀 빡세네요. 제 Suffix Array 구현은 \( O(N \log N) \)인데도 4초가 걸리는 것으로 보아, \( O(N \log^2 N) \) 구현은 정말 아슬아슬하게 통과하거나, 시간 초과가 날 것으로 보입니다.
코드
1 |
|
C. BOJ 11407 - 책 구매하기 3 (Solved at 82 min)
티어: 플래티넘 II
결정적으로 이 문제때문에 망했습니다. 팀노트를 따라치는 과정에서 ‘-=’를 ‘=-‘로 잘못 옮겨적어서 디버깅에만 근 1시간을 날렸습니다. 거기다 지난번이랑 똑같은 실수(memset을 memory.h를 include하지 않고 사용)하는 바람에 컴파일 에러도 한번 했네요. 대회때는 #include <bits/stdc++.h>를 사용하기 때문에 치명적인 실수는 아니긴 해도 이런 실수는 좀 줄여야 하는데, 쉽지 않네요..
별개로 이 문제도 책 구매하기와 동일하게 mcmf 기본문제이기 때문에, 별도의 풀이설명 없이 코드만 첨부하도록 하겠습니다.
코드
1 |
|
E. BOJ 18227 - 성대나라의 물탱크 (Not Solved)
티어: 플래티넘 III
위와 같은 트리가 있고, 1번 정점이 수도(= 루트 정점)라고 생각해봅시다. 4번 정점에 물을 채우는 상황을 생각해 봅시다. 그러면 1번 정점에 1L, 4번 정점에 2L를 채우게 됩니다. 이번에는 5번 정점에 물을 채운다고 생각해 봅시다. 이번에는 1번 정점에 1L, 2번 정점에 2L, 5번 정점에 3L를 채우게 됩니다. 이때 depth가 1인 1번 정점에는 매번 1L씩, depth가 2인 2, 4번 정점에는 2L씩, depth가 3인 5번 정점에는 3L씩 채워진다는 것을 알 수 있습니다.
여기서 추가로 해볼 수 있는 관찰은, 4번 정점에 물을 채울때, 같이 채워지는 1번 정점은 4번 정점의 조상 정점(부모 또는 부모의 조상)이고, 5번 정점에 채울 때 같이 채워지는 1, 2번 정점 또한 5번 정점의 조상입니다. 반대로 생각하면, 4번 정점은 1번 정점의 subtree에 속해 있고, 5번 정점은 1, 2번 정점의 subtree에 속해 있다고 볼 수 있습니다. 이 두 가지 관찰을 종합해 보면, 2번 쿼리가 들어올 때마다, 해당 정점의 subtree에 1번 쿼리가 행해진 횟수에 해당 정점의 depth를 곱하면 곧 답이 된다는 것을 알 수 있습니다.
다만, subtree에 1번 쿼리가 행해진 횟수를 naive하게 구하면 \( O(N^2) \)이 되어 시간 초과가 나므로, 오일러 경로 테크닉을 사용하여 세그먼트 트리로 관리해줘야 \( O(N \log N) \)에 문제를 해결할 수 있습니다
코드
1 |
|
B. BOJ 3878 - 점 분리 (Not Solved)
티어: 플래티넘 I
n(또는 m)이 1인지, 2인지, 3 이상인지에 따라 케이스가 갈립니다.
1. n, m이 둘다 1인 경우
- 같은 위치에 점이 2개인 경우가 없으므로, 무조건 YES입니다.
2. n이 2, m이 1인 경우(또는 n이 1, m이 2인 경우)
- 흰(검은) 점이 검은(흰) 점 2개를 잇는 선분에 속한다면 NO, 아니면 YES입니다.
3. n, m이 둘다 2인 경우
- 흰 점 2개를 잇는 선분과 검은 점 2개를 잇는 선분이 교차한다면 NO, 아니면 YES입니다. 4. n이 3 이상, m이 1인 경우(또는 n이 1, m이 3 이상인 경우)
- 흰(검은) 점이 검은(흰) 점의 convex hull 내부에 있지만 않다면, YES입니다. 내부에 있는지 여부 판정은, 흰(검은) 점이 \( (x, y) \)에 있다고 했을 때, 해당 점과 \( (x+1, 10001) \)를 연결하는 선분이 검은(흰) 점의 convex hull 상의 선분 중 정확히 한 개와 교차하면 흰(검은) 점이 검은(흰) convex hull 내부에 있으므로 NO, 그 외의 경우 YES를 출력하면 됩니다.
- 참고로 \( (x+1, 10001) \)를 잡은 이유는, \( (x, y) \)와 \( (x+1, 10001) \)를 잇는 선분이 지나는 정수좌표가 \( (x, y) \)와 \( (x+1, 10001) \)뿐이기 때문에, 다른 선분의 끝부분과 만나서 실제로는 한 번 교차하지만, 두 번 교차한 것으로 취급되는 경우를 방지할 수 있기 때문입니다.
5. n이 3 이상, m이 2인 경우(또는 n이 2, m이 3 이상인 경우)
- 흰(검은) 점 2개를 잇는 선분이 검은(흰) 점의 convex hull 상의 선분과 교차하는지 여부를 먼저 판정한 뒤, 아무 흰(검은) 점을 잡고 4번 항목과 동일하게 볼록다각형 내부의 점 판정을 하면 됩니다. 선분이 교차하거나, 흰 점이 볼록다강형 내부에 있다면 NO, 그 외의 경우 YES입니다.
6. n, m 모두 3 이상인 경우
- 흰 점의 convex hull과 검은 점의 convex hull상의 모든 선분 쌍에 대해 교차하는지 여부를 따진 후, 임의의 흰 점과 검은 점의 convex hull에 대해 볼록다각형 내부의 점 판정을, 그리고 임의의 검은 점과 흰 점의 convex hull에 대해서도 동일하게 볼록다각형 내부의 점 판정을 하면 됩니다.
여기에 추가로, 모든 흰(검은) 점들이 한 직선위에 있어서 다각형을 형성할 수 없는 경우, 흰(검은) 점의 개수를 2개로 취급하고, 양 끝쪽에 있는 점만 춰해주어야 합니다.
코드
1 |
|